├── .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 | Hero Graph Image 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 | [![All Contributors](https://img.shields.io/github/contributors/Borrus-sudo/Zeus?color=orange)](#contributors-) 11 | ![License](https://img.shields.io/github/license/Borrus-sudo/Zeus?label=License) 12 | ![Last Commit](https://img.shields.io/github/last-commit/Borrus-sudo/Zeus?label=Last%20Commit) 13 | ![Stars](https://img.shields.io/github/stars/Borrus-sudo/Zeus) 14 | ![Forks](https://img.shields.io/github/forks/Borrus-sudo/Zeus) 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 | ![image](https://user-images.githubusercontent.com/58482194/147904102-d13c72ac-f8cc-4b61-a102-d8946fa5a4d9.png) 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 | ![image](https://user-images.githubusercontent.com/58482194/140915256-eebd0428-194f-4caf-b2ea-e543e401fbe7.png) -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------