├── .github
├── dependabot.yml
└── workflows
│ └── contributor_list.yml
├── .gitignore
├── LICENSE
├── README.md
├── assets
└── demo.mp4
├── docs
├── config.md
├── faq.md
├── flags.md
└── installing.md
├── package.json
├── src
├── CustomRenderer.ts
├── VirtualExplorer.ts
├── fileQuery.ts
├── flagParser.ts
├── formatting.ts
├── index.ts
├── resolveConfig.ts
├── types.ts
└── utils.ts
├── tsconfig.json
├── utils
└── data-table.js
└── yarn.lock
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm" # See documentation for possible values
4 | directory: "/" # Location of package manifests
5 | schedule:
6 | interval: "daily"
7 | target-branch: "main"
8 |
--------------------------------------------------------------------------------
/.github/workflows/contributor_list.yml:
--------------------------------------------------------------------------------
1 | name: contributors
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 |
9 | jobs:
10 | contributor_list:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: cjdenio/contributor_list@master
15 | with:
16 | commit_message: 'docs: update contributor list'
17 | max_contributors: 10
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | alice.json
4 | foo.json
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 JSGandalf(he/him)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Zeus
5 | A modern cross platform ls
with powerful searching and querying capabilities to scale your productivity to the moon 🚀
6 |
7 |
8 |
9 |
10 | [](#contributors-)
11 | 
12 | 
13 | 
14 | 
15 |
16 |
17 |
18 | # 🎩 Features
19 |
20 | - 💻 A fast cross-platform `ls`
21 | - 🎨 Supports Beautiful Icons via NerdFont
22 | - 📁 Traverse a deeply nested folder structure easily!
23 | - ⚙ Powerful config system which allows you to customize stuff(like opening files in apps)
24 | - 💪 Supports FCD as well!
25 | - 🔎 An inbuilt find command which allows searching files by using the Glob pattern
26 | - 🧐 Powerful query system which allows you to see what you want
27 | - ✨ Inbuilt support for deleting, copying, pasting files and type-to-search functionality as well!
28 | - 📄 Provides extra information about files and folders!
29 |
30 | # 📝 Docs
31 |
32 | - [⚙️ Config](./docs/config.md)
33 | - [🔮 FAQ](./docs/faq.md)
34 | - [🏳 Flags](./docs/flags.md)
35 | - [📨 Installing](./docs/installing.md)
36 |
37 | # 🧬 Examples
38 | 
39 |
40 | # 🕺 Support me
41 |
42 | I am a high schooler doing Open source software. Star ⭐ the repo to encourage me to do more Open source stuff!
43 |
44 | ## 🎉 Contributing
45 |
46 |
47 |
48 | Contributions are welcome! Whether it is a small documentation change or a breaking feature, we welcome it!
49 |
50 | _Please note: All contributions are taken under the MIT license_
51 |
52 |
53 |
54 |
55 | ## 👥 Contributors
56 |
57 |
58 | - **[@Borrus-sudo](https://github.com/Borrus-sudo)**
59 |
60 | - **[@dependabot[bot]](https://github.com/apps/dependabot)**
61 |
62 | - **[@rithulkamesh](https://github.com/rithulkamesh)**
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/assets/demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Borrus-sudo/Zeus/3c22b1adb5578c7de056998cf8fd7861a68f12e4/assets/demo.mp4
--------------------------------------------------------------------------------
/docs/config.md:
--------------------------------------------------------------------------------
1 | # 📁 Configuration file
2 |
3 | ```json
4 | "ignores": [],
5 | "queryIgnores": [],
6 | "openFile": "",
7 | "icons": {},
8 | "labels":[]
9 | ```
10 |
11 | The above JSON file is the default schema of the config file.
12 |
13 | - The **ignores** property will take the name of dirent (like .git,node_modules) or a specific directory, and Zeus shall not display it.
14 | - The **queryIgnores** property will take the name of dirent (like .git,node_modules) or a specific directory, and Zeus shall not search within the matching folders or display matching files. Ignoring something does not make it queryIgnore and vice-versa.
15 | - The **openFile** property takes a string in which ${PATH} will be replaced by the file path of the pressed dirent. For e.g. "notepad ${PATH}" or "code ${PATH}". It can also be an object where the value of the matching property based on the file extension will be taken. E.g. {".js":"code ${PATH}","default":"notepad ${PATH}"}. The default property is a fallback if none of the extensions match.
16 | - The **icons** object allows users to prepend a glyph/emoji before specific files,file extensions or folders when --icons flag is passed. for e.g. {".js":"🎄","src/":"🎉"} It is important for folders to have a "/" in the ending.
17 | - The **labels** property is an array of objects of the schema `json {label: string, matchers: string[]} `. The label is the name property is passed to the **-P** flag to display all the folders or folders containing such folders that will match all the glob patterns in the `matchers` property.
18 |
19 | ### Config file example
20 |
21 | ```json
22 | {
23 | "ignores": [".git", "node_modules", "D:/Config.Msi"],
24 | "queryIgnores": ["D:/Config.Msi"],
25 | "openFile": {
26 | ".js": "code ${PATH}",
27 | "default": "notepad ${PATH}"
28 | },
29 | "icons": {
30 | ".js": "🎄",
31 | "src/": "🎉",
32 | "package.json": "📦"
33 | }
34 | }
35 | ```
--------------------------------------------------------------------------------
/docs/faq.md:
--------------------------------------------------------------------------------
1 | # Frequently Occoured Errors
2 |
3 | - If Zeus runs into an error like the one below then add the path, in this case, `D:\Config.Msi`, in `queryIgnores and ignores` in the config file
4 |
5 | 
--------------------------------------------------------------------------------
/docs/flags.md:
--------------------------------------------------------------------------------
1 | # 🏳 Flags in Zeus
2 |
3 | ```
4 | ________ _______ __ __ _______.
5 | | / | ____|| | | | / |
6 | ---/ / | |__ | | | | | (----
7 | / / | __| | | | | \ \
8 | / /----.| |____ | --- | .----) |
9 | /________||_______| \______/ |_______/
10 |
11 | Usage:
12 | $ zeus
13 | ```
14 |
15 | - **-R** → pass a regex with this flag to display all dirents matching that regex
16 | - **-B** → pass a date with this flag to display all dirents created before the given date (MM/DD/YYYY format)
17 | - **-A** → pass a date with this flag to display all dirents created after the given date (MM/DD/YYYY format)
18 | - **-P** → pass a label with this flag to display all the folders classifying as the label or folders containing these such folders.
19 | - **-fd** → pass a glob pattern to this flag to display all the files matching the glob pattern
20 | - **--ls** → pass this to start Zeus in a non-interactive mode
21 | - **--icons** → pass this to get icons based on your file extensions, the icons are customizable via the config file `.zeus.json` in your home directory.
22 | - **--help** → to get help
23 |
24 | In Zeus interactive mode (i.e. when the --ls flag is not passed) you can press `ctrl_o` on a file to open it in your preferred app (configurable via `.zeus.json` file). Pressing `ctrl_c` on a folder/file will copy its file path which will be pasted on pressing `ctrl_p` in your current working directory. When `ctrl_o` is pressed on a folder, Zeus will paste the cd command to that folder in the clipboard which can then be pasted in the terminal to FCD into it.
--------------------------------------------------------------------------------
/docs/installing.md:
--------------------------------------------------------------------------------
1 | # Installing
2 |
3 | ## Prerequisites
4 | - Install node.js from the site https://nodejs.org/en/
5 |
6 | Once node.js is installed you can clone this repo and install it globally by using the command `npm i -g`. The pkg will be soon be published to npm :)
7 |
8 | _Note I have been unable to make it an exe because https://github.com/vercel/pkg is unable to bundle files properly (due to my dep terminal-kit). Contact me at BorrisX#2830 on discord if you have a solution!_
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Zeus",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "clipboardy": "2.3.0",
8 | "copy-dir": "^1.3.0",
9 | "micromatch": "^4.0.5",
10 | "nf-icons": "^0.1.0",
11 | "pretty-bytes": "^5.6.0",
12 | "regex-parser": "^2.2.11",
13 | "terminal-kit": "^2.4.0"
14 | },
15 | "bin": {
16 | "zeus": "./dist/index.js"
17 | },
18 | "scripts": {
19 | "compile": "tsc",
20 | "compile:watch": "tsc -w",
21 | "dev": "yarn compile&&node dist/index.js"
22 | },
23 | "devDependencies": {
24 | "@types/node": "^17.0.25",
25 | "typescript": "^4.6.3"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/CustomRenderer.ts:
--------------------------------------------------------------------------------
1 | import FlagList from "./flagParser";
2 | import { contentDescriptor, FlagTypes } from "./types";
3 | import { appendGlyph } from "./utils";
4 | import formatting from './formatting';
5 |
6 | export default function (data: contentDescriptor[], term) {
7 | if (data[0].name === "../") {
8 | data.splice(0, 1);
9 | }
10 | for (const content of data) {
11 | const metaContent = `${formatting.blue}${content.meta
12 | .slice(0, 10)
13 | .padEnd(10, " ")}`;
14 |
15 | const lastModified = `${formatting.red}${content.lastModified
16 | .slice(0, 19)
17 | .padEnd(19, " ")}`;
18 |
19 | const size = `${formatting.green}${content.size
20 | .slice(0, 9)
21 | .padEnd(9, " ")}`;
22 |
23 | const indicator = `${formatting.yellow}${(content.isDir
24 | ? ""
25 | : ""
26 | ).padEnd(8, " ")}`;
27 |
28 | const dirent = `${(FlagList[FlagTypes.Icons] && content.name !== "../"
29 | ? appendGlyph(content.toPath, content.name, content.isDir)
30 | : content.name
31 | )
32 | .slice(0, 30)
33 | .padEnd(30, " ")}`;
34 |
35 | const out = `${formatting.bold
36 | }${metaContent}${formatting.padding}${size}${formatting.padding}${lastModified}${formatting.padding}${indicator}${formatting.padding}${content.isDir ? "^c" : "^w"
37 | }${dirent}`;
38 |
39 | term(out + "\n");
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/VirtualExplorer.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "child_process";
2 | import { promises as fs, Stats } from "fs";
3 | import * as path from "path";
4 | import fileQuery from "./fileQuery";
5 | import argv from "./flagParser";
6 | import { config, contentDescriptor, flagDescriptor, FlagTypes } from "./types";
7 | import {
8 | constructDescriptor,
9 | existsAsync,
10 | formatDate,
11 | getGlobalIgnores,
12 | getMetaDetails,
13 | rmDir,
14 | } from "./utils";
15 | import copydir = require("copy-dir");
16 | import clipboard = require("clipboardy");
17 |
18 | export default class {
19 | private flagList: flagDescriptor[] = argv;
20 | private globalIgnores: string[] = [
21 | ...(this.flagList[FlagTypes.GIgnore]
22 | ? this.flagList[FlagTypes.GIgnore].value.split(",")
23 | : []),
24 | ...getGlobalIgnores(),
25 | ];
26 | private ctx: string;
27 | private currContent: string;
28 | readonly Config: config;
29 | constructor(ctx: string, currContent: string, Config: config) {
30 | this.globalIgnores.push(...Config.ignores);
31 | this.ctx = ctx;
32 | this.currContent = currContent;
33 | this.Config = Config;
34 | }
35 |
36 | getFullPath(): string {
37 | return path.resolve(this.ctx, this.currContent);
38 | }
39 |
40 | async getChildren(): Promise {
41 | const fullPath: string = this.getFullPath();
42 | const exists = await existsAsync(fullPath);
43 | const fullPathStats: Stats = exists ? await fs.stat(fullPath) : null;
44 |
45 | if (exists) {
46 | const filteredFiles = await Promise.all(
47 | [...(await fs.readdir(fullPath))]
48 | .filter(
49 | (elem) =>
50 | this.globalIgnores.indexOf(elem) == -1 &&
51 | this.globalIgnores.indexOf(path.join(fullPath, elem)) == -1
52 | )
53 | .map((elem) => constructDescriptor(path.join(fullPath, elem)))
54 | );
55 |
56 | const files = [
57 | {
58 | name: "../",
59 | isDir: true,
60 | size: "",
61 | lastModified: formatDate(fullPathStats.mtime),
62 | meta: getMetaDetails(fullPathStats),
63 | toPath: path.join(fullPath, "../"),
64 | fullPath,
65 | },
66 | ...filteredFiles,
67 | ];
68 |
69 | return await fileQuery.filter(this.flagList, files);
70 | } else return [];
71 | }
72 |
73 | async commitAction(actionDescriptor): Promise<{
74 | redraw: Boolean;
75 | contents: contentDescriptor[] | null;
76 | }> {
77 | let contents: contentDescriptor[] = [];
78 | switch (actionDescriptor.verb) {
79 | case "submit":
80 | if (actionDescriptor.isDir) {
81 | const folderName = actionDescriptor.name;
82 | this.ctx = path.join(folderName, "../");
83 | this.currContent = path.basename(folderName);
84 | } else {
85 | exec(this.Config.getFileCommand(actionDescriptor.name));
86 | }
87 |
88 | contents = await this.getChildren();
89 | return { redraw: true, contents };
90 | case "open":
91 | if (actionDescriptor.isDir) {
92 | switch (process.platform) {
93 | case "win32":
94 | await clipboard.write(`pushd ${actionDescriptor.name}&cls`);
95 | break;
96 | default:
97 | await clipboard.write(`cd ${actionDescriptor.name}&clear`);
98 | }
99 | process.exit();
100 | } else {
101 | exec(this.Config.getFileCommand(actionDescriptor.name));
102 | }
103 | break;
104 |
105 | case "delete":
106 | if (actionDescriptor.isDir) {
107 | await rmDir(actionDescriptor.name);
108 | } else {
109 | await fs.unlink(actionDescriptor.name);
110 | }
111 | contents = await this.getChildren();
112 | return {
113 | redraw: true,
114 | contents,
115 | };
116 |
117 | default:
118 | if (actionDescriptor.isDir) {
119 | const toLocation = actionDescriptor.to;
120 | const folderName = path.basename(actionDescriptor.from);
121 | let destinationName = folderName;
122 | let counter = 1;
123 |
124 | while (await existsAsync(path.join(toLocation, destinationName))) {
125 | destinationName =
126 | (counter > 1 ? destinationName.replace(/\d+$/, "") : folderName) +
127 | counter;
128 | counter++;
129 | }
130 |
131 | const toPath = path.join(toLocation, destinationName);
132 | await copydir(actionDescriptor.from, toPath, {
133 | utimes: true,
134 | mode: true,
135 | cover: true,
136 | });
137 |
138 | if (actionDescriptor.verb === "cut") {
139 | await rmDir(actionDescriptor.from);
140 | }
141 | } else {
142 | const from = actionDescriptor.from; // full filePath to copy
143 | const toFolderLocation = actionDescriptor.to;
144 | let counter = 1;
145 | let { name, ext } = path.parse(from);
146 |
147 | // Choose the file name such that it does not exist
148 | while (await existsAsync(path.join(toFolderLocation, name + ext))) {
149 | name = (counter > 1 ? name.replace(/\d+$/, "") : name) + counter;
150 | counter++;
151 | }
152 |
153 | const to = path.join(toFolderLocation, name + ext);
154 | await fs.copyFile(from, to);
155 | if (actionDescriptor.verb === "cut") {
156 | await fs.unlink(from);
157 | }
158 | }
159 |
160 | contents = await this.getChildren();
161 | return { redraw: true, contents };
162 | }
163 | return { redraw: false, contents: null };
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/fileQuery.ts:
--------------------------------------------------------------------------------
1 | import { promises as fs } from "fs";
2 | import * as path from "path";
3 | import { contentDescriptor, flagDescriptor, FlagTypes } from "./types";
4 | import {
5 | cache,
6 | constructDescriptor,
7 | existsInDepth,
8 | isProject,
9 | queryIgnores,
10 | } from "./utils";
11 | import RegexParser = require("regex-parser");
12 | import micromatch = require("micromatch");
13 |
14 | const fileQuery = {
15 | async filter(
16 | FlagList: flagDescriptor[],
17 | files: contentDescriptor[]
18 | ): Promise {
19 | if (FlagList.length > 0) {
20 | const descriptor: {
21 | before: Date;
22 | after: Date;
23 | regex: RegExp;
24 | } = { before: undefined, after: undefined, regex: undefined };
25 |
26 | descriptor.before = FlagList[FlagTypes.Before]
27 | ? new Date(FlagList[FlagTypes.Before].value)
28 | : undefined;
29 |
30 | descriptor.after = FlagList[FlagTypes.After]
31 | ? new Date(FlagList[FlagTypes.After].value)
32 | : undefined;
33 |
34 | descriptor.regex = FlagList[FlagTypes.Regex]
35 | ? RegexParser(FlagList[FlagTypes.Regex].value)
36 | : undefined;
37 |
38 | const currFolderPath = files[0].fullPath;
39 |
40 | if (FlagList[FlagTypes.FilterExtension]) {
41 | const isFoundProject = cache.indexOf(currFolderPath) != -1;
42 | const isChildOfProject = cache.some((link) =>
43 | currFolderPath.startsWith(link)
44 | );
45 |
46 | if (!isFoundProject && !isChildOfProject) {
47 | const askedForLabels =
48 | FlagList[FlagTypes.FilterExtension].value.split(",");
49 |
50 | const [addFile, getProjectsLabels] = isProject();
51 | const pathContents = await fs.readdir(currFolderPath);
52 |
53 | for (let content of pathContents) {
54 | addFile(content);
55 | }
56 |
57 | const gotLabels = getProjectsLabels();
58 |
59 | const res = askedForLabels.some(
60 | (item: string) => gotLabels.indexOf(item) !== -1
61 | );
62 | const created = (await fs.stat(currFolderPath)).birthtime;
63 |
64 | const inTimeLimit = descriptor.before
65 | ? descriptor.before > created
66 | : true && descriptor.after
67 | ? descriptor.after < created
68 | : true;
69 |
70 | const matchesRegex = descriptor.regex
71 | ? descriptor.regex.test(path.basename(currFolderPath))
72 | : true;
73 |
74 | if (res && inTimeLimit && matchesRegex) {
75 | if (cache.indexOf(currFolderPath) == -1) cache.push(currFolderPath);
76 | if (FlagList[FlagTypes.Find]) {
77 | const matcher = FlagList[FlagTypes.Find].value;
78 | const content = await fileQuery.find(currFolderPath, matcher);
79 | if (content.length > 0)
80 | return await Promise.all(
81 | content.map((elem) => constructDescriptor(elem))
82 | );
83 |
84 | console.log("No results found");
85 | return process.exit();
86 | }
87 |
88 | return files;
89 | } else {
90 | let res = [];
91 |
92 | if (FlagList[FlagTypes.Find]) {
93 | const checkForFolders = files.filter((content, index) => {
94 | if (content.name === "../") {
95 | } else if (
96 | content.isDir &&
97 | queryIgnores.indexOf(content.name.slice(0, -1)) == -1 &&
98 | queryIgnores.indexOf(content.toPath) == -1
99 | ) {
100 | if (cache.some((elem) => elem.startsWith(content.toPath))) {
101 | } else {
102 | return true;
103 | }
104 | }
105 | return false;
106 | });
107 |
108 | await Promise.all(
109 | checkForFolders.map((folder) =>
110 | existsInDepth(folder.toPath, askedForLabels, descriptor)
111 | )
112 | );
113 | const content = await fileQuery.find(
114 | cache,
115 | FlagList[FlagTypes.Find].value
116 | );
117 |
118 | if (content.length < 1) {
119 | console.log("No results found");
120 | process.exit();
121 | }
122 | res = await Promise.all(
123 | content.map((elem) => constructDescriptor(elem))
124 | );
125 | } else {
126 | let goneForCheckingIndices: number[] = [];
127 | const definiteFolders = [];
128 |
129 | const checkForFolders = files.filter((content, index) => {
130 | if (content.name === "../") {
131 | definiteFolders.push(content);
132 | } else if (
133 | content.isDir &&
134 | queryIgnores.indexOf(content.name.slice(0, -1)) == -1 &&
135 | queryIgnores.indexOf(content.toPath) == -1
136 | ) {
137 | if (cache.some((elem) => elem.startsWith(content.toPath)))
138 | definiteFolders.push(content);
139 | else {
140 | goneForCheckingIndices.push(index);
141 | return true;
142 | }
143 | }
144 | return false;
145 | });
146 |
147 | res.push(...definiteFolders);
148 | (
149 | await Promise.all(
150 | checkForFolders.map((folder) =>
151 | existsInDepth(folder.toPath, askedForLabels, descriptor)
152 | )
153 | )
154 | ).forEach((elem, index) => {
155 | if (elem) {
156 | res.push(files[goneForCheckingIndices[index]]);
157 | }
158 | });
159 | }
160 | return res;
161 | }
162 | } else {
163 | if (FlagList[FlagTypes.Find]) {
164 | const matcher = FlagList[FlagTypes.Find].value;
165 | const content = await fileQuery.find(currFolderPath, matcher);
166 | if (content.length > 0) {
167 | return await Promise.all(
168 | content.map((elem) => constructDescriptor(elem))
169 | );
170 | } else {
171 | console.log("No results found");
172 | process.exit();
173 | }
174 | } else return files;
175 | }
176 | } else {
177 | if (FlagList[FlagTypes.Find]) {
178 | const matcher = FlagList[FlagTypes.Find].value;
179 | const content = await fileQuery.find(currFolderPath, matcher);
180 |
181 | if (content.length > 0) {
182 | files = await Promise.all(
183 | content.map((elem) => constructDescriptor(elem))
184 | );
185 | } else {
186 | console.log("No results found");
187 | process.exit();
188 | }
189 | }
190 |
191 | files = files.filter((file) => {
192 | if (file.name === "../") {
193 | return true;
194 | }
195 | const isBefore = descriptor.before
196 | ? descriptor.before > file.created
197 | : true;
198 | const isAfter = descriptor.after
199 | ? descriptor.after < file.created
200 | : true;
201 | if (file.created && isBefore && isAfter) {
202 | return true;
203 | }
204 | });
205 |
206 | if (descriptor.regex) {
207 | files = files.filter((elem) => {
208 | if (elem.name === "../") {
209 | return true;
210 | }
211 | return descriptor.regex.test(path.basename(elem.toPath));
212 | });
213 | }
214 |
215 | return files;
216 | }
217 | } else {
218 | return files;
219 | }
220 | },
221 |
222 | async find(dirs: string[] | string, matcher: string): Promise {
223 | if (Array.isArray(dirs)) {
224 | return [
225 | ...(
226 | await Promise.all(dirs.map((dir) => fileQuery.find(dir, matcher)))
227 | ).flat(),
228 | ];
229 | } else {
230 | // search within the received directory
231 | if (
232 | !queryIgnores.includes(dirs) &&
233 | !queryIgnores.includes(path.basename(dirs))
234 | ) {
235 | const dirents = await fs.readdir(dirs, { withFileTypes: true });
236 | const findThrough = [];
237 | const matched = [];
238 |
239 | for (let dirent of dirents) {
240 | if (dirent.isDirectory()) {
241 | if (micromatch.isMatch(dirent.name, matcher)) {
242 | matched.push(path.join(dirs, dirent.name));
243 | }
244 | findThrough.push(dirent.name);
245 | } else
246 | micromatch.isMatch(dirent.name, matcher)
247 | ? matched.push(path.join(dirs, dirent.name))
248 | : 0;
249 | }
250 |
251 | return [
252 | ...matched,
253 | ...(
254 | await Promise.all(
255 | findThrough.map((_) =>
256 | fileQuery.find(path.join(dirs, _), matcher)
257 | )
258 | )
259 | ).flat(),
260 | ].filter(Boolean);
261 | } else [];
262 | }
263 | },
264 | };
265 |
266 | export default fileQuery;
267 |
--------------------------------------------------------------------------------
/src/flagParser.ts:
--------------------------------------------------------------------------------
1 | import { normalize, resolve } from "path";
2 | import { argv } from "process";
3 | import { flagDescriptor, FlagTypes } from "./types";
4 | function getFlags(): flagDescriptor[] {
5 | let arg: string[] = argv.slice(2);
6 | const flagTypes: flagDescriptor[] = [];
7 |
8 | for (let i = 0; i < arg.length; i++) {
9 | const flag = arg[i];
10 | let val: string;
11 | switch (flag) {
12 | case "-P":
13 | val = String(arg[i + 1]);
14 | if (val === "undefined") {
15 | console.log(`No argument provided to the ${flag} flag`);
16 | process.exit();
17 | }
18 | i++;
19 | flagTypes[FlagTypes.FilterExtension] = {
20 | flag: "filterExtensions",
21 | value: val,
22 | };
23 | break;
24 |
25 | case "-B":
26 | val = String(arg[i + 1]);
27 | if (val === "undefined") {
28 | console.log(`No argument provided to the ${flag} flag`);
29 | process.exit();
30 | }
31 | i++;
32 | flagTypes[FlagTypes.Before] = { flag: "before", value: val };
33 | break;
34 |
35 | case "-A":
36 | val = String(arg[i + 1]);
37 | if (val === "undefined") {
38 | console.log(`No argument provided to the ${flag} flag`);
39 | process.exit();
40 | }
41 | i++;
42 | flagTypes[FlagTypes.After] = { flag: "after", value: val };
43 | break;
44 |
45 | case "-R":
46 | val = String(arg[i + 1]);
47 | if (val === "undefined") {
48 | console.log(`No argument provided to the ${flag} flag`);
49 | process.exit();
50 | }
51 | i++;
52 | flagTypes[FlagTypes.Regex] = { flag: "regex", value: val };
53 | break;
54 |
55 | case "-fd":
56 | val = String(arg[i + 1]);
57 | if (val === "undefined") {
58 | console.log(`No argument provided to the ${flag} flag`);
59 | process.exit();
60 | }
61 | i++;
62 | flagTypes[FlagTypes.Find] = { flag: "find", value: val };
63 | break;
64 |
65 | case "-gi":
66 | val = String(arg[i + 1]);
67 | if (val === "undefined") {
68 | console.log(`No argument provided to the ${flag} flag`);
69 | process.exit();
70 | }
71 | i++;
72 |
73 | flagTypes[FlagTypes.GIgnore] = {
74 | flag: "globalIgnore",
75 | value: val
76 | .split(",")
77 | .map((_) =>
78 | _.startsWith("./") ? resolve(process.cwd(), _) : normalize(_)
79 | )
80 | .join(","),
81 | };
82 | break;
83 |
84 | case "-qi":
85 | val = String(arg[i + 1]);
86 | if (val === "undefined") {
87 | console.log(`No argument provided to the ${flag} flag`);
88 | process.exit();
89 | }
90 | i++;
91 |
92 | flagTypes[FlagTypes.QIgnore] = {
93 | flag: "queryIgnore",
94 | value: val
95 | .split(",")
96 | .map((_) =>
97 | _.startsWith("./") ? resolve(process.cwd(), _) : normalize(_)
98 | )
99 | .join(","),
100 | };
101 | break;
102 |
103 | case "--icons":
104 | flagTypes[FlagTypes.Icons] = { flag: "icons", value: val };
105 | break;
106 |
107 | case "--ls":
108 | flagTypes[FlagTypes.LS] = { flag: "ls", value: val };
109 | break;
110 |
111 | case "--help":
112 | const content = String.raw`
113 | ________ _______ __ __ _______.
114 | | / | ____|| | | | / |
115 | ---/ / | |__ | | | | | (----
116 | / / | __| | | | | \ \
117 | / /----.| |____ | --- | .----) |
118 | /________||_______| \______/ |_______/
119 |
120 | Usage:
121 | $ zeus
122 |
123 | Options:
124 | -R flag -> pass a regex with this flag to display all dirents matching that regex
125 | -B flag -> pass a date with this flag to display all dirents created before the given date (MM/DD/YYYY format)
126 | -A flag -> pass a date with this flag to display all dirents created after the given date (MM/DD/YYYY format)
127 | -P flag -> pass a label with this flag to display all the folders classifying as the label or folders containing these such folders.
128 | -fd flag -> pass a glob pattern to this flag to display all the files matching the glob pattern
129 | --ls flag -> pass this to start Zeus in a non-interactive mode
130 | --icons flag -> pass this to get icons based on your file extensions, the icons are customizable via the config file '.zeus.json' in your home directory.
131 | --help flag -> to get help
132 |
133 | Examples:
134 | $ zeus -fd index.{.js,.ts} --ls --icons
135 | $ zeus -P node -fd index.{.js,.ts} --ls --icons
136 | $ zeus --ls --icons -R /package.json/ -B 11/11/2021
137 | `;
138 | console.log(content.trim());
139 | process.exit();
140 | }
141 | }
142 | return flagTypes;
143 | }
144 |
145 | export default getFlags();
146 |
--------------------------------------------------------------------------------
/src/formatting.ts:
--------------------------------------------------------------------------------
1 | const formatting = {
2 | bold: "^+",
3 | blue: "^b",
4 | red: "^r",
5 | green: "^g",
6 | yellow: "^y",
7 | padding: " ".repeat(1)
8 | };
9 |
10 | export default formatting;
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import * as path from "path";
3 | import { terminal as term } from "terminal-kit";
4 | import CustomRenderer from "./CustomRenderer";
5 | import FlagList from "./flagParser";
6 | import Config from "./resolveConfig";
7 | import { FlagTypes } from "./types";
8 | import { appendGlyph } from "./utils";
9 | import MagicExplorer from "./VirtualExplorer";
10 | const DataTable = require("../utils/data-table.js").DataTableFactory;
11 |
12 | (async () => {
13 | let table;
14 | const explorer = new MagicExplorer(
15 | path.dirname(process.cwd()),
16 | path.basename(process.cwd()),
17 | Config
18 | );
19 |
20 | if (FlagList[FlagTypes.LS]) {
21 | CustomRenderer(await explorer.getChildren(), term);
22 | process.exit();
23 | } else {
24 | const tableConfig = {
25 | x: 0,
26 | y: -1,
27 | width: null,
28 | height: term.height + 2,
29 | style: term.brightWhite.bgBlack,
30 | selectedStyle: term.bgBrightWhite.black,
31 | scrollPadding: 2,
32 | padding: 0.1,
33 | filterTextSize: 16,
34 | columns: [
35 | {
36 | get(content) {
37 | return content.meta;
38 | },
39 | width: 10,
40 | style() {
41 | return term.bold().blue;
42 | },
43 | },
44 | {
45 | get(content) {
46 | return content.lastModified;
47 | },
48 | width: 19,
49 | style() {
50 | return term.bold().red;
51 | },
52 | },
53 | {
54 | get(content) {
55 | return content.size;
56 | },
57 | width: 9,
58 | style() {
59 | return term.bold().green;
60 | },
61 | },
62 | {
63 | get(content) {
64 | return content.isDir ? "" : "";
65 | },
66 | width: 8,
67 | style() {
68 | return term.yellow;
69 | },
70 | },
71 | {
72 | get(content) {
73 | if (FlagList[FlagTypes.Icons] && content.name !== "../")
74 | return appendGlyph(content.toPath, content.name, content.isDir);
75 | else return content.name;
76 | },
77 | width: 30,
78 | style(item) {
79 | return item.isDir
80 | ? term.bold().colorRgb(40, 182, 217)
81 | : term.bold();
82 | },
83 | },
84 | ],
85 | };
86 |
87 | //Important callbacks
88 |
89 | const returnCallBack = (table) => {
90 | let state = "";
91 | let prevObj: { name: string; isDir: Boolean } = {
92 | name: "",
93 | isDir: undefined,
94 | };
95 |
96 | return (key) => {
97 | if (table._state.selected) {
98 | const selectedState = table._state.selected;
99 | switch (key) {
100 | case "CTRL_O":
101 | explorer
102 | .commitAction({
103 | name: selectedState.cells.fullPath
104 | ? selectedState.cells.fullPath
105 | : selectedState.cells.toPath,
106 | verb: "open",
107 | isDir: selectedState.cells.isDir,
108 | })
109 | .then(() => {});
110 | break;
111 |
112 | case "CTRL_X":
113 | state = `cut`;
114 | prevObj = {
115 | name: selectedState.cells.fullPath
116 | ? selectedState.cells.fullPath
117 | : selectedState.cells.toPath,
118 | isDir: selectedState.cells.isDir,
119 | };
120 | break;
121 |
122 | case "CTRL_D":
123 | explorer
124 | .commitAction({
125 | name: selectedState.cells.fullPath
126 | ? selectedState.cells.fullPath
127 | : selectedState.cells.toPath,
128 | verb: "delete",
129 | isDir: selectedState.cells.isDir,
130 | })
131 | .then((res) => {
132 | if (res.redraw) {
133 | table.setData(res.contents);
134 | table.promise.then(submitCallback);
135 | }
136 | });
137 | break;
138 |
139 | case "CTRL_C":
140 | state = `copy`;
141 | prevObj = {
142 | name: selectedState.cells.fullPath
143 | ? selectedState.cells.fullPath
144 | : selectedState.cells.toPath,
145 | isDir: selectedState.cells.isDir,
146 | };
147 | break;
148 |
149 | case "CTRL_P":
150 | const [verb, from, isDir] =
151 | state === "cut"
152 | ? ["cut", prevObj.name, prevObj.isDir]
153 | : ["copy", prevObj.name, prevObj.isDir];
154 |
155 | if (from) {
156 | explorer
157 | .commitAction({
158 | from,
159 | to: explorer.getFullPath(),
160 | verb,
161 | isDir,
162 | })
163 | .then((res) => {
164 | if (res.redraw) {
165 | table.setData(res.contents);
166 | table.promise.then(submitCallback);
167 | }
168 | });
169 | }
170 | break;
171 | }
172 | }
173 | };
174 | };
175 |
176 | const submitCallback = (item) => {
177 | explorer
178 | .commitAction({
179 | name: item.cells.toPath,
180 | verb: "submit",
181 | isDir: item.cells.isDir,
182 | })
183 | .then((res) => {
184 | if (res.redraw) {
185 | table.setData(res.contents);
186 | table.promise.then(submitCallback);
187 | }
188 | });
189 | };
190 |
191 | // Logic body
192 | term.clear(true);
193 | table = DataTable(term, {
194 | ...tableConfig,
195 | data: await explorer.getChildren(),
196 | });
197 | table.promise.then(submitCallback);
198 | table._term.on("key", returnCallBack(table));
199 | }
200 | })();
201 |
--------------------------------------------------------------------------------
/src/resolveConfig.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as path from "path";
3 | import { config } from "./types";
4 | const dotFileLocation = path.resolve(
5 | process.env[process.platform === "win32" ? "USERPROFILE" : "HOME"],
6 | ".zeus.json"
7 | );
8 |
9 | function getFileDefaults(): string {
10 | switch (process.platform) {
11 | case "win32":
12 | return "notepad ${PATH}";
13 | case "darwin":
14 | return "open ${PATH}";
15 | default:
16 | return "cat ${PATH}";
17 | }
18 | }
19 |
20 | function isJsonString(str) {
21 | try {
22 | JSON.parse(str);
23 | } catch (e) {
24 | return false;
25 | }
26 | return true;
27 | }
28 |
29 | function validateLabels(labels): { name: string; matchers: string[] }[] {
30 | if (Array.isArray(labels)) {
31 | const validateLabels = [];
32 | for (let label of labels) {
33 | if (typeof label === "object") {
34 | const condition1 =
35 | label.hasOwnProperty("name") && typeof label.name === "string";
36 | const condition2 =
37 | label.hasOwnProperty("matchers") && Array.isArray(label.matchers);
38 | if (condition1 && condition2) {
39 | label.matchers = label.matchers.map(String);
40 | validateLabels.push(label);
41 | }
42 | }
43 | }
44 | return validateLabels;
45 | }
46 | return [];
47 | }
48 |
49 | let options: Omit = {
50 | ignores: [],
51 | queryIgnores: [],
52 | openFile: getFileDefaults(),
53 | icons: {},
54 | labels: [],
55 | };
56 |
57 | if (!fs.existsSync(dotFileLocation)) {
58 | fs.writeFileSync(dotFileLocation, JSON.stringify(options, null, 2));
59 | } else {
60 | const dotFileConfig = fs.readFileSync(dotFileLocation, {
61 | encoding: "utf8",
62 | flag: "r",
63 | });
64 |
65 | if (dotFileConfig.trim() === "")
66 | fs.writeFileSync(dotFileLocation, JSON.stringify(options, null, 2));
67 | else {
68 | if (isJsonString(dotFileConfig)) {
69 | const parsedConfig = JSON.parse(dotFileConfig);
70 | options.ignores =
71 | parsedConfig.ignores.map((_) => path.normalize(_)) || [];
72 | options.queryIgnores =
73 | parsedConfig.queryIgnores.map((_) => path.normalize(_)) || [];
74 | options.openFile = parsedConfig.openFile || getFileDefaults();
75 | options.icons = parsedConfig.icons || {};
76 | options.labels = parsedConfig.labels
77 | ? validateLabels(parsedConfig.labels)
78 | : [];
79 | } else {
80 | console.log(
81 | ".zeus file is corrupted. Please resolve the issue for Zeus to pick the config from it"
82 | );
83 | }
84 | }
85 | }
86 |
87 | export default {
88 | ...options,
89 | getFileCommand(dir) {
90 | if (typeof options.openFile === "string") {
91 | return options.openFile.replace("${PATH}", dir);
92 | } else if (typeof options.openFile === "object") {
93 | for (let key of Object.keys(options.openFile)) {
94 | if (typeof options.openFile[key] === "string" && dir.endsWith(key)) {
95 | return options.openFile[key].replace("${PATH}", dir);
96 | }
97 | }
98 |
99 | const defaults = options.openFile["defaults"];
100 | return defaults && typeof defaults === "string"
101 | ? defaults.replace("${PATH}", dir)
102 | : getFileDefaults().replace("${PATH}", dir);
103 | }
104 | },
105 |
106 | getIcons(name: string, suffix: string): string {
107 | if (typeof options.icons === "object") {
108 | for (let key of Object.keys(options.icons)) {
109 | if (typeof options.icons[key] === "string") {
110 | if (name.endsWith(key)) {
111 | return options.icons[key] + " " + suffix;
112 | }
113 | }
114 | }
115 | }
116 | return "";
117 | },
118 | } as config;
119 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | type contentDescriptor = {
2 | name: string;
3 | isDir: boolean;
4 | size: string;
5 | lastModified: string;
6 | meta: string;
7 | toPath: string;
8 | fullPath?: string;
9 | created?: Date;
10 | };
11 |
12 | type flagDescriptor = {
13 | flag:
14 | | "filterExtensions"
15 | | "after"
16 | | "before"
17 | | "regex"
18 | | "icons"
19 | | "find"
20 | | "ls"
21 | | "queryIgnore"
22 | | "globalIgnore";
23 | value: string;
24 | };
25 |
26 | interface config {
27 | ignores: string[];
28 | queryIgnores: string[];
29 | openFile: string | Object;
30 | icons: Object;
31 | labels: { name: string; matchers: string[] }[];
32 | getFileCommand: (str: string) => string;
33 | getIcons: (str: string, suffix: string) => string;
34 | }
35 |
36 | enum FlagTypes {
37 | FilterExtension = 0,
38 | Before = 1,
39 | After = 2,
40 | Regex = 3,
41 | Icons = 4,
42 | Find = 5,
43 | LS = 6,
44 | QIgnore = 7,
45 | GIgnore = 8,
46 | }
47 |
48 | export { contentDescriptor, flagDescriptor, config, FlagTypes };
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { access, constants, promises as fs, Stats } from "fs";
2 | import { join } from "path";
3 | import FlagList from "./flagParser";
4 | import Config from "./resolveConfig";
5 | import { contentDescriptor, FlagTypes } from "./types";
6 | import path = require("path");
7 | import Icons = require("nf-icons");
8 | import prettyBytes = require("pretty-bytes");
9 | import micromatch = require("micromatch");
10 |
11 | export function getMetaDetails(stats: Stats) {
12 | let stat = "";
13 | stat += stats["mode"] & 1 ? "x" : "-";
14 | stat += stats["mode"] & 2 ? "w" : "-";
15 | stat += stats["mode"] & 4 ? "r" : "-";
16 | stat += stats["mode"] & 10 ? "x" : "-";
17 | stat += stats["mode"] & 20 ? "w" : "-";
18 | stat += stats["mode"] & 40 ? "r" : "-";
19 | stat += stats["mode"] & 100 ? "x" : "-";
20 | stat += stats["mode"] & 200 ? "w" : "-";
21 | stat += stats["mode"] & 400 ? "r" : "-";
22 | return stat;
23 | }
24 |
25 | export function formatDate(date: Date) {
26 | const options = {
27 | weekday: "short",
28 | year: "numeric",
29 | month: "short",
30 | day: "numeric",
31 | } as const;
32 | return date.toLocaleDateString("en-US", options);
33 | }
34 |
35 | export async function rmDir(path: string) {
36 | if (await existsAsync(path)) {
37 | const files = (await fs.readdir(path)) || [];
38 | for (let fileName of files) {
39 | if ((await fs.stat(join(path, fileName))).isDirectory()) {
40 | await rmDir(join(path, fileName));
41 | } else {
42 | await fs.unlink(join(path, fileName));
43 | }
44 | }
45 | }
46 | await fs.rmdir(path);
47 | }
48 |
49 | export function getGlobalIgnores(): string[] {
50 | switch (process.platform) {
51 | case "win32":
52 | return ["System Volume Information"];
53 | default:
54 | return [".Trash"];
55 | }
56 | }
57 |
58 | function getQueryIgnores(): string[] {
59 | const queryIgnores = ["node_modules", ".git"];
60 | switch (process.platform) {
61 | case "win32":
62 | return ["$RECYCLE.BIN", "System Volume Information", ...queryIgnores];
63 | default:
64 | return [".Trash", ...queryIgnores];
65 | }
66 | }
67 |
68 | export function isProject(): [(content: string) => void, () => string[]] {
69 | const configLabels = JSON.parse(JSON.stringify(Config.labels));
70 | return [
71 | (content: string) => {
72 | for (let label of configLabels) {
73 | label.matchers = label.matchers.filter((match) => {
74 | return !micromatch.isMatch(content, match);
75 | });
76 | }
77 | },
78 | () => {
79 | const isTheFollowingProjects = [];
80 | for (let label of configLabels) {
81 | if (label.matchers.length === 0) {
82 | isTheFollowingProjects.push(label.name);
83 | }
84 | }
85 | return isTheFollowingProjects;
86 | },
87 | ];
88 | }
89 |
90 | export function appendGlyph(
91 | fileName: string,
92 | suffix: string,
93 | isDir: boolean
94 | ): string {
95 | const ext = path.extname(fileName).slice(1);
96 | let glyph = Config.getIcons(isDir ? fileName + "/" : fileName, suffix);
97 |
98 | if (glyph) {
99 | return glyph;
100 | }
101 | if (isDir) {
102 | return Icons.utf16(Icons.names.MDI_FOLDER) + " " + suffix;
103 | }
104 | glyph = "";
105 | switch (ext.toLowerCase()) {
106 | case "js":
107 | glyph = Icons.names.MDI_LANGUAGE_JAVASCRIPT;
108 | break;
109 | case "rs":
110 | glyph = Icons.names.DEV_RUST;
111 | break;
112 | case "json":
113 | glyph = Icons.names.MDI_JSON;
114 | break;
115 | case "ts":
116 | glyph = Icons.names.MDI_LANGUAGE_TYPESCRIPT;
117 | break;
118 | case "java":
119 | glyph = Icons.names.DEV_JAVA;
120 | break;
121 | case "cpp":
122 | glyph = Icons.names.CUSTOM_CPP;
123 | break;
124 | case "css":
125 | glyph = Icons.names.DEV_CSS3;
126 | break;
127 | case "htm":
128 | case "html":
129 | glyph = Icons.names.DEV_HTML5;
130 | break;
131 | case "go":
132 | glyph = Icons.names.DEV_GO;
133 | break;
134 | case "py":
135 | glyph = Icons.names.DEV_PYTHON;
136 | break;
137 | case "rb":
138 | glyph = Icons.names.DEV_RUBY;
139 | break;
140 | case "pl":
141 | glyph = Icons.names.DEV_PERL;
142 | break;
143 | case "c":
144 | glyph = Icons.names.CUSTOM_C;
145 | break;
146 | case "ex":
147 | case "exs":
148 | glyph = Icons.names.CUSTOM_ELIXIR;
149 | break;
150 | case "erl":
151 | glyph = Icons.names.DEV_ERLANG;
152 | break;
153 | case "php":
154 | glyph = Icons.names.DEV_PHP;
155 | break;
156 | case "hs":
157 | case "hls":
158 | glyph = Icons.names.DEV_HASKELL;
159 | break;
160 | case "clj":
161 | case "cljs":
162 | case "cljc":
163 | case "edn":
164 | glyph = Icons.names.DEV_CLOJURE;
165 | break;
166 | case "swift":
167 | glyph = Icons.names.DEV_SWIFT;
168 | break;
169 | case "dart":
170 | glyph = Icons.names.DEV_DART;
171 | break;
172 | case "vue":
173 | glyph = Icons.names.MDI_VUEJS;
174 | break;
175 | case "svg":
176 | glyph = Icons.names.MDI_SVG;
177 | break;
178 | case "jsx":
179 | case "tsx":
180 | glyph = Icons.names.DEV_REACT;
181 | break;
182 | case "txt":
183 | glyph = Icons.names.FA_FILE_TEXT;
184 | break;
185 | case "md":
186 | glyph = Icons.names.MDI_MARKDOWN;
187 | break;
188 | case "png":
189 | case "jpg":
190 | case "jpeg":
191 | glyph = Icons.names.FA_PHOTO;
192 | break;
193 | case "pdf":
194 | glyph = Icons.names.MDI_FILE_PDF;
195 | break;
196 | case "mp4":
197 | case "avi":
198 | case "mkv":
199 | case "mov":
200 | case "wmv":
201 | case "flv":
202 | glyph = Icons.names.MDI_VIDEO;
203 | break;
204 | case "wav":
205 | case "mp3":
206 | glyph = Icons.names.MDI_SOUNDCLOUD;
207 | break;
208 | case "exe":
209 | glyph = Icons.names.OCT_FILE_BINARY;
210 | break;
211 | case "zip":
212 | glyph = Icons.names.MDI_ZIP_BOX;
213 | break;
214 | case "ttf":
215 | glyph = Icons.names.FA_FONT;
216 | break;
217 | default:
218 | break;
219 | }
220 | return (glyph ? Icons.utf16(glyph) + " " : "") + suffix;
221 | }
222 |
223 | export const queryIgnores = [
224 | ...Config.queryIgnores,
225 | ...getQueryIgnores(),
226 | ...(FlagList[FlagTypes.QIgnore]
227 | ? FlagList[FlagTypes.QIgnore].value.split(",")
228 | : []),
229 | ];
230 | export const cache: string[] = [];
231 |
232 | export async function existsInDepth(
233 | folderPath: string,
234 | askedForLabels: string[],
235 | descriptor: {
236 | before: undefined | Date;
237 | after: undefined | Date;
238 | regex: RegExp | undefined;
239 | }
240 | ): Promise {
241 | const contents = await fs.readdir(folderPath, { withFileTypes: true });
242 | const [addFile, getProjectsLabels] = isProject();
243 |
244 | const dirs = [];
245 | for (let content of contents) {
246 | const contentPath = path.join(folderPath, content.name);
247 | addFile(content.name);
248 | if (
249 | content.isDirectory() &&
250 | !queryIgnores.includes(path.basename(contentPath)) &&
251 | !queryIgnores.includes(contentPath)
252 | ) {
253 | dirs.push(contentPath);
254 | }
255 | }
256 |
257 | const gotLabels = getProjectsLabels();
258 | const res = askedForLabels.some(
259 | (item: string) => gotLabels.indexOf(item) !== -1
260 | );
261 | const stat = await fs.stat(folderPath);
262 | const created = stat.birthtime;
263 | const inTimeLimit = descriptor.before
264 | ? descriptor.before > created
265 | : true && descriptor.after
266 | ? descriptor.after < created
267 | : true;
268 | const matchesRegex = descriptor.regex
269 | ? descriptor.regex.test(path.basename(folderPath))
270 | : true;
271 |
272 | if (res && inTimeLimit && matchesRegex) {
273 | if (cache.indexOf(folderPath) == -1) cache.push(folderPath);
274 | return true;
275 | } else {
276 | const responses = await Promise.all(
277 | dirs.map((dir) => existsInDepth(dir, askedForLabels, descriptor))
278 | );
279 | return responses.some((elem) => elem === true);
280 | }
281 | }
282 |
283 | export async function constructDescriptor(
284 | dirent: string
285 | ): Promise {
286 | const stats = await fs.lstat(dirent);
287 | const elem = path.basename(dirent);
288 |
289 | if (!stats.isSymbolicLink()) {
290 | let isDir = stats.isDirectory();
291 | return {
292 | name: isDir ? elem + "/" : elem,
293 | isDir,
294 | size: isDir ? "" : prettyBytes(stats.size),
295 | lastModified: formatDate(stats.mtime),
296 | meta: getMetaDetails(stats),
297 | toPath: dirent,
298 | created: stats.birthtime,
299 | };
300 | } else {
301 | const target = path.resolve(
302 | path.dirname(dirent),
303 | path.normalize(await fs.readlink(dirent))
304 | );
305 | return {
306 | name: `${path.basename(dirent)} -> ${path.basename(target)}`,
307 | isDir: stats.isDirectory(),
308 | size: prettyBytes(stats.size),
309 | lastModified: formatDate(stats.mtime),
310 | meta: getMetaDetails(stats),
311 | toPath: target,
312 | created: stats.birthtime,
313 | };
314 | }
315 | }
316 |
317 | export async function existsAsync(pathName: string) {
318 | return new Promise(function (resolve, _reject) {
319 | access(pathName, constants.R_OK, (err: any) => {
320 | if (err) {
321 | resolve(false);
322 | } else {
323 | resolve(true);
324 | }
325 | });
326 | });
327 | }
328 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES6",
5 | "outDir": "dist",
6 | "experimentalDecorators": true,
7 | "downlevelIteration": true,
8 | "baseUrl": ".",
9 | "moduleResolution": "node"
10 | },
11 | "exclude": ["node_modules"],
12 | "include": ["./src/**/*", "utils"]
13 | }
14 |
--------------------------------------------------------------------------------
/utils/data-table.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is taken from @dave-newson repo https://github.com/dave-newson/terminal-kit-plugins
3 | */
4 | const events = require("events");
5 |
6 | const defaultKeyBindings = {
7 | ENTER: "submit",
8 | KP_ENTER: "submit",
9 | UP: "previousRow",
10 | DOWN: "nextRow",
11 | LEFT: "previousCol",
12 | RIGHT: "nextCol",
13 | TAB: "nextRow",
14 | SHIFT_TAB: "previousRow",
15 | HOME: "firstRow",
16 | END: "lastRow",
17 | BACKSPACE: "deleteLetter",
18 | ESCAPE: "cancel",
19 | };
20 |
21 | function getColumn(getMethod, targetItem) {
22 | let getter;
23 | if (typeof getMethod === "string") {
24 | getter = (item) => item[String(getMethod)];
25 | } else {
26 | getter = getMethod;
27 | }
28 | return getter(targetItem);
29 | }
30 | class TableConfig {
31 | constructor(terminal, options) {
32 | options.width = options.width || terminal.width;
33 | options.height = options.height || terminal.height;
34 | this.keyBindings = options.keyBindings || defaultKeyBindings;
35 | if (!options.allowCancel) {
36 | delete this.keyBindings.ESCAPE;
37 | }
38 | this.x = options.x || 0;
39 | this.y = options.y || 0;
40 | this.style = {
41 | default: options.style || terminal.bgBlack.brightWhite,
42 | selected: options.selectedStyle || terminal.bgBrightYellow.black,
43 | };
44 | this.scrollPadding = options.scrollPadding || 3;
45 | this.padding = options.padding || 1;
46 | this.columns = options.columns || [];
47 | this.columns.forEach((col) => {
48 | if (typeof col.get === "string") {
49 | const getStr = col.get;
50 | col.get = (item) => {
51 | return item[getStr];
52 | };
53 | } else if (typeof col.get !== "function") {
54 | throw Error('Columns must define a "get" property.');
55 | }
56 | });
57 | this.filterTextSize = options.filterTextSize || 16;
58 | }
59 | }
60 | class RowItem {
61 | constructor(index, data) {
62 | this.cells = data;
63 | this.visible = true;
64 | this.index = index;
65 | }
66 | }
67 | class TableState extends events.EventEmitter {
68 | constructor(config, options) {
69 | super();
70 | this.paused = false;
71 | this.displayArea = {
72 | x: 1,
73 | y: 1,
74 | width: 0,
75 | height: 0,
76 | xScroll: 0,
77 | yScroll: 0,
78 | };
79 | this._filter = "";
80 | this.config = config;
81 | this.paused = options.paused || false;
82 | this.displayArea.x = options.x || 1;
83 | this.displayArea.y = options.y || 1;
84 | this.displayArea.width = options.width || 0;
85 | this.displayArea.height = options.height || 0;
86 | this.data = options.data || [];
87 | }
88 | get items() {
89 | return this.data;
90 | }
91 | set items(items) {
92 | this.data = [];
93 | let i = 0;
94 | items.forEach((item) => {
95 | this.data.push(new RowItem(i, item));
96 | i++;
97 | });
98 | this.refilter();
99 | this.emit("change");
100 | }
101 | get filter() {
102 | return String(this._filter);
103 | }
104 | set filter(text) {
105 | this._filter = String(text).slice(0, this.config.filterTextSize);
106 | this.refilter();
107 | this.emit("change");
108 | }
109 | get selectedIndex() {
110 | return this._selected;
111 | }
112 | set selectedIndex(index) {
113 | if (index !== undefined && this.data[index] === undefined) {
114 | index = undefined;
115 | }
116 | this._selected = index;
117 | this.emit("change");
118 | }
119 | get selected() {
120 | if (this._selected !== undefined) {
121 | return this.data[this._selected];
122 | }
123 | return null;
124 | }
125 | set selected(item) {
126 | if (item === undefined) {
127 | if (this._selected && this.data[this._selected].visible === false) {
128 | this.selectedIndex = undefined;
129 | }
130 | } else {
131 | this.selectedIndex = item.index;
132 | }
133 | }
134 | getFilteredItems() {
135 | return this.items.filter((item) => item.visible);
136 | }
137 | refilter() {
138 | this.items.forEach((item) => {
139 | const found = this.config.columns.find((column) => {
140 | if (typeof this.config.filter === "function") {
141 | return this.config.filter(this.filter, item);
142 | } else {
143 | return (
144 | String(getColumn(column.get, item.cells))
145 | .toUpperCase()
146 | .indexOf(this.filter.toUpperCase()) > -1
147 | );
148 | }
149 | });
150 | item.visible = found !== undefined;
151 | });
152 | }
153 | }
154 | class DataTable extends events.EventEmitter {
155 | constructor(terminal, options) {
156 | super();
157 | this.options = options;
158 | this.grabbing = false;
159 | this._term = terminal;
160 | this._config = new TableConfig(this._term, options);
161 | this._state = new TableState(this._config, options);
162 | this.setData(options.data || []);
163 | this._events = {
164 | onKeyPress: this.onKeyPress.bind(this),
165 | redraw: this.redraw.bind(this),
166 | };
167 | this._state.on("change", this._events.redraw.bind(this));
168 | this._term.on("key", this._events.onKeyPress.bind(this));
169 | if (!this.grabbing) {
170 | this._term.grabInput(true);
171 | }
172 | this.promise = new Promise((resolve, reject) => {
173 | this._state.resolve = resolve;
174 | this._state.reject = reject;
175 | if (this._state.data.length) {
176 | this.redraw();
177 | }
178 | this.emit("ready");
179 | });
180 | }
181 | setSelected(item) {
182 | this._state.selected = item;
183 | }
184 | submit(isSubmit) {
185 | const data = isSubmit ? this._state.selected : null;
186 | if (this._state.resolve) {
187 | this._state.resolve(data);
188 | }
189 | }
190 | onKeyPress(key) {
191 | if (this._state.paused) {
192 | return;
193 | }
194 | if (key === "ESCAPE") {
195 | process.exit();
196 | }
197 | const items = this._state.getFilteredItems();
198 | const selectedItemIndex = items.findIndex(
199 | (item) => item.index === this._state.selectedIndex
200 | );
201 | switch (this._config.keyBindings[key]) {
202 | case "submit":
203 | this.submit(true);
204 | break;
205 | case "previousRow":
206 | this.setSelected(items[selectedItemIndex - 1]);
207 | break;
208 | case "nextRow":
209 | this.setSelected(items[selectedItemIndex + 1]);
210 | break;
211 | case "firstRow":
212 | this.setSelected(items[0]);
213 | break;
214 | case "nextCol":
215 | break;
216 | case "previousCol":
217 | break;
218 | case "lastRow":
219 | this.setSelected(items[items.length - 1]);
220 | break;
221 | case "cancel":
222 | this.submit(false);
223 | break;
224 | case "deleteLetter":
225 | this._state.filter = this._state.filter.slice(0, -1);
226 | break;
227 | default:
228 | if (key.length === 1) {
229 | this._state.filter += key;
230 | }
231 | break;
232 | }
233 | }
234 | redraw() {
235 | if (this._state.paused) {
236 | return;
237 | }
238 | if (this._config.y !== undefined) {
239 | this._term.moveTo(1, this._config.y);
240 | }
241 | const filterHeight = 2;
242 | const filteredItems = this._state.getFilteredItems();
243 | if (
244 | filteredItems.length &&
245 | filteredItems.filter((item) => item.index === this._state.selectedIndex)
246 | .length === 0
247 | ) {
248 | this._state.selectedIndex = filteredItems[0].index;
249 | }
250 | let height = this._state.displayArea.height - filterHeight;
251 | if (filteredItems.length > height) {
252 | height -= 2;
253 | }
254 | let pos = 0;
255 | filteredItems.forEach((item) => {
256 | if (item.index === this._state.selectedIndex) {
257 | if (pos < this._state.displayArea.yScroll) {
258 | this._state.displayArea.yScroll = pos;
259 | }
260 | if (pos >= this._state.displayArea.yScroll + height) {
261 | this._state.displayArea.yScroll = 1 + (pos - height);
262 | }
263 | }
264 | pos++;
265 | });
266 | const visibleItems = filteredItems.slice(
267 | this._state.displayArea.yScroll,
268 | this._state.displayArea.yScroll + height
269 | );
270 | let cursorPos = this._state.displayArea.y;
271 | this._term.moveTo(this._state.displayArea.x, cursorPos);
272 | cursorPos += filterHeight;
273 | this._term.moveTo(this._state.displayArea.x, cursorPos++);
274 | if (this._state.displayArea.yScroll > 0) {} else {
275 | this._config.style.default(
276 | String().padEnd(this._state.displayArea.width, " ")
277 | );
278 | }
279 | let row = 0;
280 | visibleItems.forEach((item) => {
281 | row++;
282 | this._term.moveTo(this._state.displayArea.x, cursorPos++);
283 | this._config.columns.forEach((column) => {
284 | let output = this._config.style.default;
285 | if (this._state.selectedIndex === item.index) {
286 | output = this._config.style.selected;
287 | } else if (typeof column.style === "function") {
288 | output = column.style(item.cells);
289 | }
290 | const text = String(getColumn(column.get, item.cells))
291 | .slice(0, column.width)
292 | .padEnd(column.width + this._config.padding, " ")
293 | .padStart(column.width + this._config.padding * 2, " ");
294 | output(text);
295 | });
296 | });
297 | for (; row < height; row++) {
298 | this._term.moveTo(this._state.displayArea.x, cursorPos++);
299 | this._config.style.default(
300 | String().padEnd(this._state.displayArea.width, " ")
301 | );
302 | }
303 | this._term.moveTo(this._state.displayArea.x, cursorPos++);
304 | }
305 | pause() {
306 | this._state.paused = true;
307 | }
308 | resume() {
309 | this._state.paused = false;
310 | }
311 | focus(giveFocus = true) {
312 | if (giveFocus) {
313 | this.resume();
314 | } else {
315 | this.pause();
316 | }
317 | }
318 | setData(data) {
319 | this._state.items = data;
320 | this._state.filter = "";
321 | this.promise = new Promise((resolve, reject) => {
322 | this._state.resolve = resolve;
323 | this._state.reject = reject;
324 | if (this._state.data.length) {
325 | this.redraw();
326 | }
327 | this.emit("ready");
328 | });
329 | }
330 | }
331 |
332 | function DataTableFactory(terminal, options) {
333 | return new DataTable(terminal, options);
334 | }
335 | exports.DataTableFactory = DataTableFactory;
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@cronvel/get-pixels@^3.4.0":
6 | version "3.4.0"
7 | resolved "https://registry.yarnpkg.com/@cronvel/get-pixels/-/get-pixels-3.4.0.tgz#697cd691c16bbb8b29ed596da73fd6a7e9a2f34d"
8 | integrity sha512-do5jDoX9oCR/dGHE4POVQ3PYDCmQ2Fow4CA72UL4WoE8zUImA/0lChczjfl+ucNjE4sXFWUnzoO6j4WzrUvLnw==
9 | dependencies:
10 | jpeg-js "^0.4.1"
11 | ndarray "^1.0.19"
12 | ndarray-pack "^1.1.1"
13 | node-bitmap "0.0.1"
14 | omggif "^1.0.10"
15 | pngjs "^5.0.0"
16 |
17 | "@types/node@^17.0.25":
18 | version "17.0.25"
19 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
20 | integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==
21 |
22 | arch@^2.1.1:
23 | version "2.2.0"
24 | resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
25 | integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
26 |
27 | braces@^3.0.2:
28 | version "3.0.2"
29 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
30 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
31 | dependencies:
32 | fill-range "^7.0.1"
33 |
34 | chroma-js@^2.1.2:
35 | version "2.1.2"
36 | resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-2.1.2.tgz#1075cb9ae25bcb2017c109394168b5cf3aa500ec"
37 | integrity sha512-ri/ouYDWuxfus3UcaMxC1Tfp3IE9K5iQzxc2hSxbBRVNQFut1UuGAsZmiAf2mOUubzGJwgMSv9lHg+XqLaz1QQ==
38 | dependencies:
39 | cross-env "^6.0.3"
40 |
41 | clipboardy@2.3.0:
42 | version "2.3.0"
43 | resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290"
44 | integrity sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==
45 | dependencies:
46 | arch "^2.1.1"
47 | execa "^1.0.0"
48 | is-wsl "^2.1.1"
49 |
50 | copy-dir@^1.3.0:
51 | version "1.3.0"
52 | resolved "https://registry.yarnpkg.com/copy-dir/-/copy-dir-1.3.0.tgz#8c65130e11d8313a6ac2c0578e4c6c6f70b456ba"
53 | integrity sha512-Q4+qBFnN4bwGwvtXXzbp4P/4iNk0MaiGAzvQ8OiMtlLjkIKjmNN689uVzShSM0908q7GoFHXIPx4zi75ocoaHw==
54 |
55 | cross-env@^6.0.3:
56 | version "6.0.3"
57 | resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-6.0.3.tgz#4256b71e49b3a40637a0ce70768a6ef5c72ae941"
58 | integrity sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==
59 | dependencies:
60 | cross-spawn "^7.0.0"
61 |
62 | cross-spawn@^6.0.0:
63 | version "6.0.5"
64 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
65 | integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
66 | dependencies:
67 | nice-try "^1.0.4"
68 | path-key "^2.0.1"
69 | semver "^5.5.0"
70 | shebang-command "^1.2.0"
71 | which "^1.2.9"
72 |
73 | cross-spawn@^7.0.0:
74 | version "7.0.3"
75 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
76 | integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
77 | dependencies:
78 | path-key "^3.1.0"
79 | shebang-command "^2.0.0"
80 | which "^2.0.1"
81 |
82 | cwise-compiler@^1.1.2:
83 | version "1.1.3"
84 | resolved "https://registry.yarnpkg.com/cwise-compiler/-/cwise-compiler-1.1.3.tgz#f4d667410e850d3a313a7d2db7b1e505bb034cc5"
85 | integrity sha1-9NZnQQ6FDToxOn0tt7HlBbsDTMU=
86 | dependencies:
87 | uniq "^1.0.0"
88 |
89 | end-of-stream@^1.1.0:
90 | version "1.4.4"
91 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
92 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
93 | dependencies:
94 | once "^1.4.0"
95 |
96 | execa@^1.0.0:
97 | version "1.0.0"
98 | resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
99 | integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
100 | dependencies:
101 | cross-spawn "^6.0.0"
102 | get-stream "^4.0.0"
103 | is-stream "^1.1.0"
104 | npm-run-path "^2.0.0"
105 | p-finally "^1.0.0"
106 | signal-exit "^3.0.0"
107 | strip-eof "^1.0.0"
108 |
109 | fill-range@^7.0.1:
110 | version "7.0.1"
111 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
112 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
113 | dependencies:
114 | to-regex-range "^5.0.1"
115 |
116 | get-stream@^4.0.0:
117 | version "4.1.0"
118 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
119 | integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
120 | dependencies:
121 | pump "^3.0.0"
122 |
123 | iota-array@^1.0.0:
124 | version "1.0.0"
125 | resolved "https://registry.yarnpkg.com/iota-array/-/iota-array-1.0.0.tgz#81ef57fe5d05814cd58c2483632a99c30a0e8087"
126 | integrity sha1-ge9X/l0FgUzVjCSDYyqZwwoOgIc=
127 |
128 | is-buffer@^1.0.2:
129 | version "1.1.6"
130 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
131 | integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
132 |
133 | is-docker@^2.0.0:
134 | version "2.2.1"
135 | resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
136 | integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
137 |
138 | is-number@^7.0.0:
139 | version "7.0.0"
140 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
141 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
142 |
143 | is-stream@^1.1.0:
144 | version "1.1.0"
145 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
146 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
147 |
148 | is-wsl@^2.1.1:
149 | version "2.2.0"
150 | resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
151 | integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
152 | dependencies:
153 | is-docker "^2.0.0"
154 |
155 | isexe@^2.0.0:
156 | version "2.0.0"
157 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
158 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
159 |
160 | jpeg-js@^0.4.1:
161 | version "0.4.3"
162 | resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b"
163 | integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==
164 |
165 | lazyness@^1.2.0:
166 | version "1.2.0"
167 | resolved "https://registry.yarnpkg.com/lazyness/-/lazyness-1.2.0.tgz#5dc0f02c37280436b21f0e4918ce6e72a109c657"
168 | integrity sha512-KenL6EFbwxBwRxG93t0gcUyi0Nw0Ub31FJKN1laA4UscdkL1K1AxUd0gYZdcLU3v+x+wcFi4uQKS5hL+fk500g==
169 |
170 | micromatch@^4.0.5:
171 | version "4.0.5"
172 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
173 | integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
174 | dependencies:
175 | braces "^3.0.2"
176 | picomatch "^2.3.1"
177 |
178 | ndarray-pack@^1.1.1:
179 | version "1.2.1"
180 | resolved "https://registry.yarnpkg.com/ndarray-pack/-/ndarray-pack-1.2.1.tgz#8caebeaaa24d5ecf70ff86020637977da8ee585a"
181 | integrity sha1-jK6+qqJNXs9w/4YCBjeXfajuWFo=
182 | dependencies:
183 | cwise-compiler "^1.1.2"
184 | ndarray "^1.0.13"
185 |
186 | ndarray@^1.0.13, ndarray@^1.0.19:
187 | version "1.0.19"
188 | resolved "https://registry.yarnpkg.com/ndarray/-/ndarray-1.0.19.tgz#6785b5f5dfa58b83e31ae5b2a058cfd1ab3f694e"
189 | integrity sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==
190 | dependencies:
191 | iota-array "^1.0.0"
192 | is-buffer "^1.0.2"
193 |
194 | nextgen-events@^1.5.2:
195 | version "1.5.2"
196 | resolved "https://registry.yarnpkg.com/nextgen-events/-/nextgen-events-1.5.2.tgz#72b2b6cbe6912d9be2165188d0169027e65d8ebe"
197 | integrity sha512-0ZEIRQywH5Oxt2IYYufRltQg/KjXhKM7f7MHve+ZIRaKnIR1PPYEXAl2WBmej5Sf0Qh2GgE/21sMRZVuOyxLzw==
198 |
199 | nf-icons@^0.1.0:
200 | version "0.1.0"
201 | resolved "https://registry.yarnpkg.com/nf-icons/-/nf-icons-0.1.0.tgz#1d1f87eef6c471ee557f344fc299218926c064a7"
202 | integrity sha512-yVpZlYXEsLouCGGucfd7x4Dt1Di5nV4Fjs3TtsaT0o280Y/pjHGvVB+4Z9ldKEJGsJKqXK7B6DfmmJPDM1e33g==
203 |
204 | nice-try@^1.0.4:
205 | version "1.0.5"
206 | resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
207 | integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
208 |
209 | node-bitmap@0.0.1:
210 | version "0.0.1"
211 | resolved "https://registry.yarnpkg.com/node-bitmap/-/node-bitmap-0.0.1.tgz#180eac7003e0c707618ef31368f62f84b2a69091"
212 | integrity sha1-GA6scAPgxwdhjvMTaPYvhLKmkJE=
213 |
214 | npm-run-path@^2.0.0:
215 | version "2.0.2"
216 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
217 | integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
218 | dependencies:
219 | path-key "^2.0.0"
220 |
221 | omggif@^1.0.10:
222 | version "1.0.10"
223 | resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19"
224 | integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==
225 |
226 | once@^1.3.1, once@^1.4.0:
227 | version "1.4.0"
228 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
229 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
230 | dependencies:
231 | wrappy "1"
232 |
233 | p-finally@^1.0.0:
234 | version "1.0.0"
235 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
236 | integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
237 |
238 | path-key@^2.0.0, path-key@^2.0.1:
239 | version "2.0.1"
240 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
241 | integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
242 |
243 | path-key@^3.1.0:
244 | version "3.1.1"
245 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
246 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
247 |
248 | picomatch@^2.3.1:
249 | version "2.3.1"
250 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
251 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
252 |
253 | pngjs@^5.0.0:
254 | version "5.0.0"
255 | resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
256 | integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
257 |
258 | pretty-bytes@^5.6.0:
259 | version "5.6.0"
260 | resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
261 | integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
262 |
263 | pump@^3.0.0:
264 | version "3.0.0"
265 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
266 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
267 | dependencies:
268 | end-of-stream "^1.1.0"
269 | once "^1.3.1"
270 |
271 | regex-parser@^2.2.11:
272 | version "2.2.11"
273 | resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58"
274 | integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==
275 |
276 | semver@^5.5.0:
277 | version "5.7.1"
278 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
279 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
280 |
281 | setimmediate@^1.0.5:
282 | version "1.0.5"
283 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
284 | integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
285 |
286 | seventh@^0.7.40:
287 | version "0.7.40"
288 | resolved "https://registry.yarnpkg.com/seventh/-/seventh-0.7.40.tgz#a5a010496cb84421bb81f524840484a5aa473be9"
289 | integrity sha512-7sxUydQx4iEh17uJUFjZDAwbffJirldZaNIJvVB/hk9mPEL3J4GpLGSL+mHFH2ydkye46DAsLGqzFJ+/Qj5foQ==
290 | dependencies:
291 | setimmediate "^1.0.5"
292 |
293 | shebang-command@^1.2.0:
294 | version "1.2.0"
295 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
296 | integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
297 | dependencies:
298 | shebang-regex "^1.0.0"
299 |
300 | shebang-command@^2.0.0:
301 | version "2.0.0"
302 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
303 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
304 | dependencies:
305 | shebang-regex "^3.0.0"
306 |
307 | shebang-regex@^1.0.0:
308 | version "1.0.0"
309 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
310 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
311 |
312 | shebang-regex@^3.0.0:
313 | version "3.0.0"
314 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
315 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
316 |
317 | signal-exit@^3.0.0:
318 | version "3.0.5"
319 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
320 | integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
321 |
322 | string-kit@^0.16.0:
323 | version "0.16.0"
324 | resolved "https://registry.yarnpkg.com/string-kit/-/string-kit-0.16.0.tgz#63a1d7b7afd0d2b2214b6ab0d82040b2047d0676"
325 | integrity sha512-xiJvm0KiJrOYDFzANh/LnMXEujnveaxcmspx9rg4r5qaIvex9b77h7fa6Ba+/0kEZXMvYcOS6Y7vJzK8lAUbUg==
326 |
327 | strip-eof@^1.0.0:
328 | version "1.0.0"
329 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
330 | integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
331 |
332 | terminal-kit@^2.4.0:
333 | version "2.4.0"
334 | resolved "https://registry.yarnpkg.com/terminal-kit/-/terminal-kit-2.4.0.tgz#d572601ee3e25bcf1c05e223990436fce8f1a075"
335 | integrity sha512-lQCKNFYCaVoFM23pcurnQ7wOnsz4u588JNu2sfNOnB8IU6Tl4vdOdHNe7bL2aIiB0kA7m94gS4VI0+3CRI1G/A==
336 | dependencies:
337 | "@cronvel/get-pixels" "^3.4.0"
338 | chroma-js "^2.1.2"
339 | lazyness "^1.2.0"
340 | ndarray "^1.0.19"
341 | nextgen-events "^1.5.2"
342 | seventh "^0.7.40"
343 | string-kit "^0.16.0"
344 | tree-kit "^0.7.4"
345 |
346 | to-regex-range@^5.0.1:
347 | version "5.0.1"
348 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
349 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
350 | dependencies:
351 | is-number "^7.0.0"
352 |
353 | tree-kit@^0.7.4:
354 | version "0.7.4"
355 | resolved "https://registry.yarnpkg.com/tree-kit/-/tree-kit-0.7.4.tgz#d6c9dba3e06188952282f196657f6bbbb454a39a"
356 | integrity sha512-Of3tPmVs3b6BhzyUJ7t0olisf47kYr9qAm0XaUpURMjdBn6TwiVaaMuTFoKkkvPGojd9trKAHlrGGcGKcdR1DA==
357 |
358 | typescript@^4.6.3:
359 | version "4.6.3"
360 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
361 | integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
362 |
363 | uniq@^1.0.0:
364 | version "1.0.1"
365 | resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
366 | integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
367 |
368 | which@^1.2.9:
369 | version "1.3.1"
370 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
371 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
372 | dependencies:
373 | isexe "^2.0.0"
374 |
375 | which@^2.0.1:
376 | version "2.0.2"
377 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
378 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
379 | dependencies:
380 | isexe "^2.0.0"
381 |
382 | wrappy@1:
383 | version "1.0.2"
384 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
385 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
386 |
--------------------------------------------------------------------------------