├── .babelrc
├── .editorconfig
├── .eslintrc
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmrc
├── .travis.yml
├── .vscode
└── settings.json
├── License.md
├── README.md
├── appveyor.yml
├── assets
└── demo.gif
├── dist
├── cli.js
├── formats.js
└── helpers.js
├── gulpfile.js
├── package.json
├── src
├── cli.js
├── formats.js
└── helpers.js
├── test
└── testOrganize.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env"
4 | ],
5 | "plugins": []
6 | }
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | indent_style = space
7 | indent_size = 2
8 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "no-console": "off",
5 | "strict": "off",
6 | "prefer-const": "off",
7 | "no-restricted-syntax": "off",
8 | "comma-dangle": "off",
9 | "no-unused-vars": "off",
10 | "import/no-extraneous-dependencies": "off"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guidelines
2 |
3 | 1. Please check whether another person has raised a [pull request](https://github.com/manrajgrover/organize-cli/pulls) for same issue before creating one.
4 | 2. Please check [issues](https://github.com/manrajgrover/organize-cli/issues) created before requesting for a feature.
5 | 3. Open a pull request explaining what changes it brings.
6 | 4. Add references where applicable.
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | ## Description
13 |
14 |
15 | ### Versions
16 |
17 | - NodeJS version (if applicable):
18 | - Organize CLI version:
19 |
20 | ### Error
21 |
22 |
23 | ### Expected behaviour
24 |
25 |
26 | ## Steps to recreate
27 |
28 |
29 | ## People to notify
30 |
31 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | ## Description of new feature, or changes
10 |
11 |
12 | ## Checklist
13 |
14 | - [ ] Your branch is up-to-date with the base branch
15 | - [ ] You've included at least one test if this is a new feature
16 | - [ ] All tests are passing
17 |
18 | ## Related Issues and Discussions
19 |
20 |
21 |
22 | ## People to notify
23 |
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /testing
3 |
4 |
5 | .nyc_output
6 | package-lock.json
7 |
8 | npm-debug.log
9 | yarn-error.log
10 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 7.4
4 | install:
5 | - npm install
6 | script:
7 | - npm link
8 | - npm run lint
9 | - npm run test
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[javascript]": {
3 | "editor.formatOnSave": false
4 | },
5 | "editor.formatOnSave": true,
6 | "eslint.autoFixOnSave": true
7 | }
8 |
--------------------------------------------------------------------------------
/License.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Manraj Singh
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # organize-cli
2 | [](https://travis-ci.org/manrajgrover/organize-cli) [](https://ci.appveyor.com/project/manrajgrover/organize-cli)
3 | [](https://www.npmjs.com/package/organize-cli) [](https://www.npmjs.com/package/organize-cli) 
4 |
5 | > Organize files based on file types, formally known as `organizeit`
6 |
7 |
8 |
9 | ## Installation
10 |
11 | ```
12 | $ npm install -g organize-cli
13 | ```
14 |
15 | ## Usage
16 |
17 | ```
18 | Usage: organize [options]
19 |
20 | Options:
21 | -o, --output Output directory - Creates one if doesn't exist [string]
22 | -d, --date Organize files by dates [boolean]
23 | -s, --source Source directory to organize [string] [required]
24 | -t, --type Specific types to organize - strings of file extensions [array]
25 | -f, --folder Specific folder to move specific files to [string]
26 | -h, --help Show help [boolean]
27 |
28 | Examples:
29 | organize -s ~/Downloads -o . -t mp3 wav -f "Songs"
30 | ```
31 |
32 | ## Development
33 |
34 | Run:
35 |
36 | ```sh
37 | $ git clone https://github.com/manrajgrover/organize-cli.git
38 | $ cd organize-cli
39 | $ npm link
40 | ```
41 |
42 | This will setup a symbolic link to the CLI. Any changes in source files will now be reflected when running the `organize` command.
43 |
44 | To lint your code, run
45 |
46 | ```sh
47 | $ npm run lint
48 | ```
49 |
50 | ## Like it?
51 |
52 | :star2: this repo to show support. You can also tweet about this project by clicking [here](https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Fmanrajgrover%2Forganize-cli&via=manrajsgrover&text=Checkout%20this%20command%20line%20tool%20for%20organizing%20your%20files%20in%20a%20better%20way%20on%20%23Github&hashtags=cli%2C%20node).
53 |
54 | ## Related
55 |
56 | * [classifier](https://github.com/bhrigu123/classifier)
57 |
58 | ## License
59 | [MIT](https://github.com/manrajgrover/organize-cli/blob/master/License.md) © Manraj Singh
60 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | nodejs_version: "7.4"
3 |
4 | install:
5 | - ps: Install-Product node $env:nodejs_version
6 | - npm install
7 |
8 | test_script:
9 | - node --version
10 | - npm --version
11 | - npm link
12 | - npm run lint
13 | - npm run test
14 |
15 | build: off
16 |
--------------------------------------------------------------------------------
/assets/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manrajgrover/organize-cli/47d025fe70ac087d1990f3ba2d39dd378c0482c9/assets/demo.gif
--------------------------------------------------------------------------------
/dist/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 |
4 | 'use strict';
5 |
6 | var yargs = require('yargs');
7 | var fs = require('fs');
8 | var path = require('path');
9 | var ora = require('ora');
10 |
11 | /**
12 | * Get helper functions from `helpers`
13 | */
14 |
15 | var _require = require('./helpers'),
16 | organizeByDefaults = _require.organizeByDefaults,
17 | organizeBySpecificFileTypes = _require.organizeBySpecificFileTypes,
18 | organizeByDates = _require.organizeByDates;
19 |
20 | /**
21 | * Pass all arguments passed using `yargs`
22 | */
23 |
24 |
25 | var argv = yargs.usage('Usage: $0 [options]').alias('o', 'output').describe('o', "Output directory - Creates one if doesn't exist").string('o').alias('d', 'date').describe('d', 'Organize files by dates').boolean('d').alias('s', 'source').describe('s', 'Source directory to organize').string('s').alias('t', 'type').describe('t', 'Specific types to organize - strings of file extensions').array('t').alias('f', 'folder').describe('f', 'Specific folder to move specific files to').string('f').alias('l', 'list').describe('l', 'List the mv commands that will be executed without actually executing them').boolean('l').demand(['s']).example('$0 -s ~/Downloads -o . -t mp3 wav -f "Songs"').help('h').alias('h', 'help').argv;
26 |
27 | /**
28 | * Spinner initialization
29 | */
30 | var spinner = ora('Scanning').start();
31 |
32 | /**
33 | * Get source directory, if provided in arguments
34 | * Defaults to current working directory
35 | */
36 | var sourceDir = argv.source ? path.resolve(process.cwd(), argv.source) : process.cwd();
37 | /**
38 | * Get output directory, if provided in arguments
39 | * Defaults to source directory
40 | */
41 | var outputDir = argv.output ? path.resolve(process.cwd(), argv.output) : sourceDir;
42 |
43 | var names = fs.readdirSync(sourceDir);
44 | var moved = [];
45 |
46 | var listOnly = argv.l;
47 |
48 | // If date flag is passed, organize by dates
49 | if (argv.d) {
50 | moved = organizeByDates(names, sourceDir, outputDir, spinner, listOnly);
51 | } else if (argv.t && argv.f) {
52 | // Organize specific file formats and move to specific folder
53 | var spFormats = argv.t;
54 | var spFolder = argv.f;
55 |
56 | moved = organizeBySpecificFileTypes(spFormats, spFolder, names, sourceDir, outputDir, spinner, listOnly);
57 | } else {
58 | // Defaults to normal behavior
59 | moved = organizeByDefaults(names, sourceDir, outputDir, spinner, listOnly);
60 | }
61 |
62 | /**
63 | * Resolves all promises and catches any error
64 | * while moving a file
65 | */
66 | Promise.all(moved.map(function (p) {
67 | return p.catch(function (e) {
68 | return e;
69 | });
70 | })).then(function (messages) {
71 | var isError = false;
72 |
73 | // Check if any promise failed
74 | var _iteratorNormalCompletion = true;
75 | var _didIteratorError = false;
76 | var _iteratorError = undefined;
77 |
78 | try {
79 | for (var _iterator = messages[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
80 | var message = _step.value;
81 |
82 | if (message instanceof Error) {
83 | spinner.fail("Couldn't move all files!");
84 | isError = true;
85 | break;
86 | }
87 | }
88 | } catch (err) {
89 | _didIteratorError = true;
90 | _iteratorError = err;
91 | } finally {
92 | try {
93 | if (!_iteratorNormalCompletion && _iterator.return) {
94 | _iterator.return();
95 | }
96 | } finally {
97 | if (_didIteratorError) {
98 | throw _iteratorError;
99 | }
100 | }
101 | }
102 |
103 | if (!listOnly && !isError) {
104 | spinner.succeed('Moved all files!');
105 | }
106 | }).catch(function (err) {
107 | return spinner.fail('An error occured!');
108 | });
--------------------------------------------------------------------------------
/dist/formats.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Music = ['MP3', 'WAV', 'WMA', 'MKA', 'AAC', 'MID', 'RA', 'RAM', 'RM', 'OGG'];
4 | var Codes = ['CPP', 'RB', 'PY', 'HTML', 'CSS', 'JS', 'PHP'];
5 | var Compressed = ['RAR', 'JAR', 'ZIP', 'TAR', 'MAR', 'ISO', 'LZ', '7ZIP', 'TGZ', 'GZ', 'BZ2'];
6 | var Documents = ['DOC', 'DOCX', 'PPT', 'PPTX', 'PAGES', 'PDF', 'ODT', 'ODP', 'XLSX', 'XLS', 'ODS', 'TXT', 'IN', 'OUT', 'MD'];
7 | var Images = ['JPG', 'JPEG', 'GIF', 'PNG', 'SVG'];
8 | var Executables = ['DEB', 'EXE', 'SH', 'BUNDLE'];
9 | var Video = ['FLV', 'WMV', 'MOV', 'MP4', 'MPEG', '3GP', 'MKV'];
10 |
11 | module.exports = {
12 | Music: Music,
13 | Codes: Codes,
14 | Compressed: Compressed,
15 | Documents: Documents,
16 | Images: Images,
17 | Video: Video,
18 | Executables: Executables
19 | };
--------------------------------------------------------------------------------
/dist/helpers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var mv = require('mv');
4 | var fs = require('fs');
5 | var path = require('path');
6 | var dateformat = require('dateformat');
7 | var formats = require('./formats');
8 |
9 | /**
10 | * Check if file is valid
11 | *
12 | * @param {string} name Name of file
13 | * @param {string} dir File directory
14 | */
15 | var isValidFile = function isValidFile(name, dir) {
16 | return name.indexOf('.') !== 0 && !fs.statSync(path.join(dir, name)).isDirectory();
17 | };
18 |
19 | /**
20 | * Create a directory if it does not exist
21 | *
22 | * @param {string} folderPath Path of folder to be created
23 | */
24 | var mkdir = function mkdir(folderPath) {
25 | try {
26 | fs.mkdirSync(folderPath);
27 | } catch (err) {
28 | if (err.code !== 'EEXIST') {
29 | throw new Error('Error occurred while creating a new directory');
30 | }
31 | }
32 | };
33 |
34 | /**
35 | * Get extension of a file
36 | *
37 | * @param {string} fileName File name
38 | */
39 | var getFileExtension = function getFileExtension(fileName) {
40 | var i = fileName.lastIndexOf('.');
41 | return i < 0 ? '' : fileName.substr(i + 1);
42 | };
43 |
44 | /**
45 | * Returns a promise for movement of file to specific directory;
46 | * Also creates the output directory if not existing
47 | *
48 | * @param {Object} spinner Ora spinner instance
49 | * @param {string} source Source directory name
50 | * @param {string} output Output directory name
51 | * @param {string} fileName File name
52 | * @param {string} type File type
53 | * @param {boolean} listOnly Only list the commands which will be executed for movement
54 | */
55 | var organize = function organize(spinner, source, output, fileName, type, listOnly) {
56 | var typeDir = path.resolve(output, type);
57 |
58 | // Create the directory only if listOnly is not set
59 | if (!listOnly) {
60 | mkdir(output);
61 | mkdir(typeDir);
62 | }
63 |
64 | // Return promise for moving a specific file to specific directory
65 | return new Promise(function (resolve, reject) {
66 | // If listOnly is set, output the command that will be executed without
67 | // moving the file
68 | if (listOnly) {
69 | var listMessage = 'mv ' + path.resolve(source, fileName) + ' ' + path.resolve(typeDir, fileName);
70 | spinner.info(listMessage);
71 | resolve(listMessage);
72 | } else {
73 | // Move the file
74 | mv(path.resolve(source, fileName), path.resolve(typeDir, fileName), function (err) {
75 | if (err) {
76 | var errorMessage = 'Couldn\'t move ' + fileName + ' because of following error: ' + err;
77 | spinner.warn(errorMessage);
78 | reject(new Error(errorMessage));
79 | } else {
80 | var successMessage = 'Moved ' + fileName + ' to ' + type + ' folder';
81 | spinner.info(successMessage);
82 | resolve(successMessage);
83 | }
84 | });
85 | }
86 | });
87 | };
88 |
89 | /**
90 | * Organizes files using pre-configured formats and file extensions
91 | *
92 | * @param {Array} files File names
93 | * @param {string} sourceDir Source directory name
94 | * @param {string} outputDir Output directory name
95 | * @param {Object} spinner Ora spinner instance
96 | * @param {boolean} listOnly Only list the commands which will be executed for movement
97 | */
98 | var organizeByDefaults = function organizeByDefaults(files, sourceDir, outputDir, spinner, listOnly) {
99 | var moved = [];
100 |
101 | var _iteratorNormalCompletion = true;
102 | var _didIteratorError = false;
103 | var _iteratorError = undefined;
104 |
105 | try {
106 | for (var _iterator = files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
107 | var file = _step.value;
108 |
109 | // Check if file is valid
110 | if (isValidFile(file, sourceDir)) {
111 | // Get file extension
112 | var extension = getFileExtension(file).toUpperCase();
113 | var isMoved = false;
114 |
115 | // Iterating over format types
116 | var _iteratorNormalCompletion2 = true;
117 | var _didIteratorError2 = false;
118 | var _iteratorError2 = undefined;
119 |
120 | try {
121 | for (var _iterator2 = Object.keys(formats)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
122 | var type = _step2.value;
123 |
124 | if (formats[type].indexOf(extension) >= 0) {
125 | // Output to spinner that this file will be moved
126 | spinner.info('Moving file ' + file + ' to ' + type);
127 |
128 | // Move the file to format directory
129 | var pOrganize = organize(spinner, sourceDir, outputDir, file, type, listOnly);
130 |
131 | // Push the promise to array
132 | moved.push(pOrganize);
133 | isMoved = true;
134 | break;
135 | }
136 | }
137 |
138 | // If file extension does not exist in config,
139 | // move the file to Miscellaneous folder
140 | } catch (err) {
141 | _didIteratorError2 = true;
142 | _iteratorError2 = err;
143 | } finally {
144 | try {
145 | if (!_iteratorNormalCompletion2 && _iterator2.return) {
146 | _iterator2.return();
147 | }
148 | } finally {
149 | if (_didIteratorError2) {
150 | throw _iteratorError2;
151 | }
152 | }
153 | }
154 |
155 | if (!isMoved) {
156 | // Output to spinner that this file will be moved
157 | spinner.info('Moving file ' + file + ' to Miscellaneous');
158 |
159 | // Push the promise to array
160 | moved.push(organize(spinner, sourceDir, outputDir, file, 'Miscellaneous', listOnly));
161 | }
162 | }
163 | }
164 | } catch (err) {
165 | _didIteratorError = true;
166 | _iteratorError = err;
167 | } finally {
168 | try {
169 | if (!_iteratorNormalCompletion && _iterator.return) {
170 | _iterator.return();
171 | }
172 | } finally {
173 | if (_didIteratorError) {
174 | throw _iteratorError;
175 | }
176 | }
177 | }
178 |
179 | return moved;
180 | };
181 |
182 | /**
183 | * Organize specific file types
184 | *
185 | * @param {Array} spFormats Organize only specific formats
186 | * @param {string} spFolder Move specific files to this folder name
187 | * @param {Array} files File names
188 | * @param {string} sourceDir Source directory name
189 | * @param {string} outputDir Output directory name
190 | * @param {Object} spinner Ora spinner instance
191 | * @param {boolean} listOnly Only list the commands which will be executed for movement
192 | */
193 | var organizeBySpecificFileTypes = function organizeBySpecificFileTypes(spFormats, spFolder, files, sourceDir, outputDir, spinner, listOnly) {
194 | // Filter file names on specific formats
195 | var names = files.filter(function (name) {
196 | if (!isValidFile(name, sourceDir)) {
197 | return false;
198 | }
199 |
200 | var extension = getFileExtension(name);
201 | return spFormats.indexOf(extension) !== -1;
202 | });
203 |
204 | var moved = [];
205 |
206 | var _iteratorNormalCompletion3 = true;
207 | var _didIteratorError3 = false;
208 | var _iteratorError3 = undefined;
209 |
210 | try {
211 | for (var _iterator3 = names[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
212 | var name = _step3.value;
213 |
214 | // Output to spinner that this file will be moved
215 | spinner.info('Moving file ' + name + ' to ' + spFolder);
216 |
217 | // Move the file to output directory
218 | var pOrganize = organize(spinner, sourceDir, outputDir, name, spFolder, listOnly);
219 |
220 | // Push the promise to array
221 | moved.push(pOrganize);
222 | }
223 | } catch (err) {
224 | _didIteratorError3 = true;
225 | _iteratorError3 = err;
226 | } finally {
227 | try {
228 | if (!_iteratorNormalCompletion3 && _iterator3.return) {
229 | _iterator3.return();
230 | }
231 | } finally {
232 | if (_didIteratorError3) {
233 | throw _iteratorError3;
234 | }
235 | }
236 | }
237 |
238 | return moved;
239 | };
240 |
241 | /**
242 | * Organizes the files by creation date
243 | *
244 | * @param {Array} files Files to be organized
245 | * @param {string} sourceDir Source directory name
246 | * @param {string} outputDir Output directory name
247 | * @param {object} spinner Ora spinner instance
248 | * @param {boolean} listOnly Only list the commands which will be executed for movement
249 | */
250 | var organizeByDates = function organizeByDates(files, sourceDir, outputDir, spinner, listOnly) {
251 | var moved = [];
252 |
253 | var _iteratorNormalCompletion4 = true;
254 | var _didIteratorError4 = false;
255 | var _iteratorError4 = undefined;
256 |
257 | try {
258 | for (var _iterator4 = files[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
259 | var file = _step4.value;
260 |
261 | // Get date when the file was created
262 | var date = fs.statSync(path.join(sourceDir, file));
263 | date = dateformat(new Date(date.mtime), 'yyyy-mm-dd');
264 |
265 | // Output to spinner that this file will be moved
266 | spinner.info('Moving file ' + file + ' to ' + date + ' folder');
267 |
268 | // Move the file to output directory
269 | var pOrganize = organize(spinner, sourceDir, outputDir, file, date, listOnly);
270 |
271 | // Push the promise to array
272 | moved.push(pOrganize);
273 | }
274 | } catch (err) {
275 | _didIteratorError4 = true;
276 | _iteratorError4 = err;
277 | } finally {
278 | try {
279 | if (!_iteratorNormalCompletion4 && _iterator4.return) {
280 | _iterator4.return();
281 | }
282 | } finally {
283 | if (_didIteratorError4) {
284 | throw _iteratorError4;
285 | }
286 | }
287 | }
288 |
289 | return moved;
290 | };
291 |
292 | module.exports = {
293 | mkdir: mkdir,
294 | getFileExtension: getFileExtension,
295 | organize: organize,
296 | organizeByDefaults: organizeByDefaults,
297 | organizeBySpecificFileTypes: organizeBySpecificFileTypes,
298 | organizeByDates: organizeByDates
299 | };
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const babel = require('gulp-babel');
3 |
4 | gulp.task('es6', () => {
5 | gulp.src('src/*.js')
6 | .pipe(babel({
7 | presets: ['env'],
8 | }))
9 | .pipe(gulp.dest('./dist'));
10 | });
11 |
12 | gulp.task('watch', () => {
13 | gulp.watch(['./src/*.js'], ['es6']);
14 | });
15 |
16 | gulp.task('default', ['es6']);
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "organize-cli",
3 | "version": "0.5.8",
4 | "description": "Organize your files and folders in just seconds",
5 | "scripts": {
6 | "lint": "eslint ./src/cli.js ./src/formats.js ./src/helpers.js",
7 | "test": "npm link && mocha --recursive --timeout 10000",
8 | "report": "nyc mocha --recursive",
9 | "all": "npm run test && npm run lint"
10 | },
11 | "bin": {
12 | "organize": "./dist/cli.js"
13 | },
14 | "preferGlobal": true,
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/manrajgrover/organize-cli.git"
18 | },
19 | "keywords": [
20 | "organize",
21 | "cli",
22 | "classify",
23 | "files",
24 | "folders",
25 | "npm",
26 | "organize-cli"
27 | ],
28 | "maintainers": [
29 | {
30 | "name": "manrajgrover",
31 | "email": "manrajsinghgrover@gmail.com"
32 | }
33 | ],
34 | "author": "Manraj Singh (http://manrajsingh.in)",
35 | "license": "MIT",
36 | "bugs": {
37 | "url": "https://github.com/manrajgrover/organize-cli/issues"
38 | },
39 | "homepage": "https://github.com/manrajgrover/organize-cli#readme",
40 | "dependencies": {
41 | "dateformat": "^2.0.0",
42 | "mv": "^2.1.1",
43 | "ora": "^1.3.0",
44 | "path": "^0.12.7",
45 | "yargs": "^4.8.1"
46 | },
47 | "devDependencies": {
48 | "babel-preset-env": "^1.7.0",
49 | "command-exists": "^1.2.6",
50 | "eslint": "^5.3.0",
51 | "eslint-config-airbnb": "17.1.0",
52 | "eslint-config-prettier": "^3.1.0",
53 | "eslint-plugin-import": "^2.14.0",
54 | "eslint-plugin-jsx-a11y": "^6.1.1",
55 | "eslint-plugin-prettier": "^3.0.0",
56 | "eslint-plugin-react": "^7.11.0",
57 | "fs-extra": "^3.0.1",
58 | "gulp": "^3.9.1",
59 | "gulp-babel": "^6.1.2",
60 | "mocha": "^5.2.0",
61 | "nyc": "^11.0.3",
62 | "prettier": "^1.14.3",
63 | "sync-exec": "^0.6.2"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict';
4 |
5 | const yargs = require('yargs');
6 | const fs = require('fs');
7 | const path = require('path');
8 | const ora = require('ora');
9 |
10 | /**
11 | * Get helper functions from `helpers`
12 | */
13 | const {
14 | organizeByDefaults,
15 | organizeBySpecificFileTypes,
16 | organizeByDates
17 | } = require('./helpers');
18 |
19 |
20 | /**
21 | * Pass all arguments passed using `yargs`
22 | */
23 | // eslint-disable-next-line prefer-destructuring
24 | const argv = yargs
25 | .usage('Usage: $0 [options]')
26 | .alias('o', 'output')
27 | .describe('o', "Output directory - Creates one if doesn't exist")
28 | .string('o')
29 | .alias('d', 'date')
30 | .describe('d', 'Organize files by dates')
31 | .boolean('d')
32 | .alias('s', 'source')
33 | .describe('s', 'Source directory to organize')
34 | .string('s')
35 | .alias('t', 'type')
36 | .describe('t', 'Specific types to organize - strings of file extensions')
37 | .array('t')
38 | .alias('f', 'folder')
39 | .describe('f', 'Specific folder to move specific files to')
40 | .string('f')
41 | .alias('l', 'list')
42 | .describe('l', 'List the mv commands that will be executed without actually executing them')
43 | .boolean('l')
44 | .demand(['s'])
45 | .example('$0 -s ~/Downloads -o . -t mp3 wav -f "Songs"')
46 | .help('h')
47 | .alias('h', 'help')
48 | .argv;
49 |
50 | /**
51 | * Spinner initialization
52 | */
53 | let spinner = ora('Scanning').start();
54 |
55 | /**
56 | * Get source directory, if provided in arguments
57 | * Defaults to current working directory
58 | */
59 | const sourceDir = argv.source ? path.resolve(
60 | process.cwd(), argv.source
61 | ) : process.cwd();
62 | /**
63 | * Get output directory, if provided in arguments
64 | * Defaults to source directory
65 | */
66 | const outputDir = argv.output ? path.resolve(
67 | process.cwd(), argv.output
68 | ) : sourceDir;
69 |
70 | let names = fs.readdirSync(sourceDir);
71 | let moved = [];
72 |
73 | let listOnly = argv.l;
74 |
75 | // If date flag is passed, organize by dates
76 | if (argv.d) {
77 | moved = organizeByDates(names, sourceDir, outputDir, spinner, listOnly);
78 | } else if (argv.t && argv.f) {
79 | // Organize specific file formats and move to specific folder
80 | const spFormats = argv.t;
81 | const spFolder = argv.f;
82 |
83 | moved = organizeBySpecificFileTypes(
84 | spFormats, spFolder, names, sourceDir, outputDir, spinner, listOnly
85 | );
86 | } else {
87 | // Defaults to normal behavior
88 | moved = organizeByDefaults(names, sourceDir, outputDir, spinner, listOnly);
89 | }
90 |
91 | /**
92 | * Resolves all promises and catches any error
93 | * while moving a file
94 | */
95 | Promise.all(moved.map(p => p.catch(e => e)))
96 | .then((messages) => {
97 | let isError = false;
98 |
99 | // Check if any promise failed
100 | for (let message of messages) {
101 | if (message instanceof Error) {
102 | spinner.fail("Couldn't move all files!");
103 | isError = true;
104 | break;
105 | }
106 | }
107 |
108 | if (!listOnly && !isError) {
109 | spinner.succeed('Moved all files!');
110 | }
111 | })
112 | .catch(err => spinner.fail('An error occured!'));
113 |
--------------------------------------------------------------------------------
/src/formats.js:
--------------------------------------------------------------------------------
1 | const Music = ['MP3', 'WAV', 'WMA', 'MKA', 'AAC', 'MID', 'RA', 'RAM', 'RM', 'OGG'];
2 | const Codes = ['CPP', 'RB', 'PY', 'HTML', 'CSS', 'JS', 'PHP'];
3 | const Compressed = ['RAR', 'JAR', 'ZIP', 'TAR', 'MAR', 'ISO', 'LZ', '7ZIP', 'TGZ', 'GZ', 'BZ2'];
4 | const Documents = ['DOC', 'DOCX', 'PPT', 'PPTX', 'PAGES', 'PDF', 'ODT', 'ODP', 'XLSX', 'XLS', 'ODS', 'TXT', 'IN', 'OUT', 'MD'];
5 | const Images = ['JPG', 'JPEG', 'GIF', 'PNG', 'SVG'];
6 | const Executables = ['DEB', 'EXE', 'SH', 'BUNDLE'];
7 | const Video = ['FLV', 'WMV', 'MOV', 'MP4', 'MPEG', '3GP', 'MKV'];
8 |
9 | module.exports = {
10 | Music,
11 | Codes,
12 | Compressed,
13 | Documents,
14 | Images,
15 | Video,
16 | Executables,
17 | };
18 |
--------------------------------------------------------------------------------
/src/helpers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mv = require('mv');
4 | const fs = require('fs');
5 | const path = require('path');
6 | const dateformat = require('dateformat');
7 | const formats = require('./formats');
8 |
9 | /**
10 | * Check if file is valid
11 | *
12 | * @param {string} name Name of file
13 | * @param {string} dir File directory
14 | */
15 | const isValidFile = (name, dir) => (name.indexOf('.') !== 0
16 | && !fs.statSync(path.join(dir, name)).isDirectory());
17 |
18 | /**
19 | * Create a directory if it does not exist
20 | *
21 | * @param {string} folderPath Path of folder to be created
22 | */
23 | const mkdir = (folderPath) => {
24 | try {
25 | fs.mkdirSync(folderPath);
26 | } catch (err) {
27 | if (err.code !== 'EEXIST') {
28 | throw new Error('Error occurred while creating a new directory');
29 | }
30 | }
31 | };
32 |
33 | /**
34 | * Get extension of a file
35 | *
36 | * @param {string} fileName File name
37 | */
38 | const getFileExtension = (fileName) => {
39 | const i = fileName.lastIndexOf('.');
40 | return (i < 0) ? '' : fileName.substr(i + 1);
41 | };
42 |
43 | /**
44 | * Returns a promise for movement of file to specific directory;
45 | * Also creates the output directory if not existing
46 | *
47 | * @param {Object} spinner Ora spinner instance
48 | * @param {string} source Source directory name
49 | * @param {string} output Output directory name
50 | * @param {string} fileName File name
51 | * @param {string} type File type
52 | * @param {boolean} listOnly Only list the commands which will be executed for movement
53 | */
54 | const organize = (spinner, source, output, fileName, type, listOnly) => {
55 | const typeDir = path.resolve(output, type);
56 |
57 | // Create the directory only if listOnly is not set
58 | if (!listOnly) {
59 | mkdir(output);
60 | mkdir(typeDir);
61 | }
62 |
63 | // Return promise for moving a specific file to specific directory
64 | return new Promise((resolve, reject) => {
65 | // If listOnly is set, output the command that will be executed without
66 | // moving the file
67 | if (listOnly) {
68 | const listMessage = `mv ${path.resolve(source, fileName)} ${path.resolve(typeDir, fileName)}`;
69 | spinner.info(listMessage);
70 | resolve(listMessage);
71 | } else {
72 | // Move the file
73 | mv(path.resolve(source, fileName), path.resolve(typeDir, fileName), (err) => {
74 | if (err) {
75 | const errorMessage = `Couldn't move ${fileName} because of following error: ${err}`;
76 | spinner.warn(errorMessage);
77 | reject(new Error(errorMessage));
78 | } else {
79 | const successMessage = `Moved ${fileName} to ${type} folder`;
80 | spinner.info(successMessage);
81 | resolve(successMessage);
82 | }
83 | });
84 | }
85 | });
86 | };
87 |
88 | /**
89 | * Organizes files using pre-configured formats and file extensions
90 | *
91 | * @param {Array} files File names
92 | * @param {string} sourceDir Source directory name
93 | * @param {string} outputDir Output directory name
94 | * @param {Object} spinner Ora spinner instance
95 | * @param {boolean} listOnly Only list the commands which will be executed for movement
96 | */
97 | const organizeByDefaults = (files, sourceDir, outputDir, spinner, listOnly) => {
98 | const moved = [];
99 |
100 | for (let file of files) {
101 | // Check if file is valid
102 | if (isValidFile(file, sourceDir)) {
103 | // Get file extension
104 | const extension = getFileExtension(file).toUpperCase();
105 | let isMoved = false;
106 |
107 | // Iterating over format types
108 | for (let type of Object.keys(formats)) {
109 | if (formats[type].indexOf(extension) >= 0) {
110 | // Output to spinner that this file will be moved
111 | spinner.info(`Moving file ${file} to ${type}`);
112 |
113 | // Move the file to format directory
114 | const pOrganize = organize(spinner, sourceDir, outputDir, file, type, listOnly);
115 |
116 | // Push the promise to array
117 | moved.push(pOrganize);
118 | isMoved = true;
119 | break;
120 | }
121 | }
122 |
123 | // If file extension does not exist in config,
124 | // move the file to Miscellaneous folder
125 | if (!isMoved) {
126 | // Output to spinner that this file will be moved
127 | spinner.info(`Moving file ${file} to Miscellaneous`);
128 |
129 | // Push the promise to array
130 | moved.push(
131 | organize(spinner, sourceDir, outputDir, file, 'Miscellaneous', listOnly)
132 | );
133 | }
134 | }
135 | }
136 |
137 | return moved;
138 | };
139 |
140 | /**
141 | * Organize specific file types
142 | *
143 | * @param {Array} spFormats Organize only specific formats
144 | * @param {string} spFolder Move specific files to this folder name
145 | * @param {Array} files File names
146 | * @param {string} sourceDir Source directory name
147 | * @param {string} outputDir Output directory name
148 | * @param {Object} spinner Ora spinner instance
149 | * @param {boolean} listOnly Only list the commands which will be executed for movement
150 | */
151 | const organizeBySpecificFileTypes = (
152 | spFormats, spFolder, files, sourceDir, outputDir, spinner, listOnly
153 | ) => {
154 | // Filter file names on specific formats
155 | const names = files.filter((name) => {
156 | if (!isValidFile(name, sourceDir)) {
157 | return false;
158 | }
159 |
160 | const extension = getFileExtension(name);
161 | return spFormats.indexOf(extension) !== -1;
162 | });
163 |
164 | const moved = [];
165 |
166 | for (let name of names) {
167 | // Output to spinner that this file will be moved
168 | spinner.info(`Moving file ${name} to ${spFolder}`);
169 |
170 | // Move the file to output directory
171 | const pOrganize = organize(spinner, sourceDir, outputDir, name, spFolder, listOnly);
172 |
173 | // Push the promise to array
174 | moved.push(pOrganize);
175 | }
176 |
177 | return moved;
178 | };
179 |
180 | /**
181 | * Organizes the files by creation date
182 | *
183 | * @param {Array} files Files to be organized
184 | * @param {string} sourceDir Source directory name
185 | * @param {string} outputDir Output directory name
186 | * @param {object} spinner Ora spinner instance
187 | * @param {boolean} listOnly Only list the commands which will be executed for movement
188 | */
189 | const organizeByDates = (files, sourceDir, outputDir, spinner, listOnly) => {
190 | const moved = [];
191 |
192 | for (let file of files) {
193 | // Get date when the file was created
194 | let date = fs.statSync(path.join(sourceDir, file));
195 | date = dateformat(new Date(date.mtime), 'yyyy-mm-dd');
196 |
197 | // Output to spinner that this file will be moved
198 | spinner.info(`Moving file ${file} to ${date} folder`);
199 |
200 | // Move the file to output directory
201 | const pOrganize = organize(spinner, sourceDir, outputDir, file, date, listOnly);
202 |
203 | // Push the promise to array
204 | moved.push(pOrganize);
205 | }
206 |
207 | return moved;
208 | };
209 |
210 | module.exports = {
211 | mkdir,
212 | getFileExtension,
213 | organize,
214 | organizeByDefaults,
215 | organizeBySpecificFileTypes,
216 | organizeByDates
217 | };
218 |
--------------------------------------------------------------------------------
/test/testOrganize.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | const assert = require('assert');
3 | const fs = require('fs');
4 | const fse = require('fs-extra');
5 | const path = require('path');
6 | const syncExec = require('sync-exec');
7 | const commandExistsSync = require('command-exists').sync;
8 |
9 | const { mkdir } = require('../src/helpers');
10 | const formats = require('../src/formats');
11 |
12 | const TESTING_FOLDER = path.join(__dirname, '..', 'testing');
13 |
14 | const SOURCE_FOLDER = path.join(TESTING_FOLDER, 'source');
15 | const OUTPUT_FOLDER = path.join(TESTING_FOLDER, 'output');
16 |
17 | const removeDirsFromFolder = (dirName) => {
18 | let files;
19 | try {
20 | files = fs.readdirSync(dirName);
21 | } catch (e) {
22 | console.log(`Couldn't read directory because of error: ${e}`);
23 | }
24 |
25 | if (files.length > 0) {
26 | for (let i = 0; i < files.length; i += 1) {
27 | let filePath = path.join(dirName, files[i]);
28 | if (fs.statSync(filePath).isDirectory()) {
29 | fse.removeSync(filePath);
30 | }
31 | }
32 | }
33 | };
34 |
35 | describe('Organize Files', () => {
36 | beforeEach(() => {
37 | mkdir(TESTING_FOLDER);
38 | removeDirsFromFolder(TESTING_FOLDER);
39 |
40 | mkdir(SOURCE_FOLDER);
41 | mkdir(OUTPUT_FOLDER);
42 |
43 | for (let folderType of Object.keys(formats)) {
44 | for (let fileType of formats[folderType]) {
45 | fileType = fileType.toLowerCase();
46 | const FILE_NAME = `test.${fileType}`;
47 | fs.writeFileSync(path.join(SOURCE_FOLDER, FILE_NAME), '');
48 | }
49 | }
50 |
51 | // Some Miscellaneous files in source
52 | fs.writeFileSync(path.join(SOURCE_FOLDER, 'test'), '');
53 | fs.writeFileSync(path.join(SOURCE_FOLDER, 'test.apib'), '');
54 | fs.writeFileSync(path.join(SOURCE_FOLDER, 'test.ai'), '');
55 | fs.writeFileSync(path.join(SOURCE_FOLDER, 'test.log'), '');
56 |
57 | // Some Miscellaneous files in source
58 | fs.utimesSync(path.join(SOURCE_FOLDER, 'test'), '1499599912', '1499599912');
59 | fs.utimesSync(path.join(SOURCE_FOLDER, 'test.apib'), '1499699912', '1499699912');
60 | fs.utimesSync(path.join(SOURCE_FOLDER, 'test.ai'), '1499299912', '1499299912');
61 | fs.utimesSync(path.join(SOURCE_FOLDER, 'test.log'), '1499399912', '1499399912');
62 |
63 | if (!commandExistsSync('organize')) {
64 | throw new Error('Command "organize" command not found');
65 | }
66 | });
67 |
68 | it('should throw error for missing args', () => {
69 | const { stderr } = syncExec('organize');
70 |
71 | assert.notEqual(stderr, '');
72 | assert(stderr.includes('Missing required argument: s'));
73 | });
74 |
75 | it('should organize files with source', () => {
76 | syncExec(`organize -s ${SOURCE_FOLDER}`);
77 |
78 | for (let folderType of Object.keys(formats)) {
79 | for (let fileType of formats[folderType]) {
80 | fileType = fileType.toLowerCase();
81 | assert(fs.existsSync(path.join(SOURCE_FOLDER, folderType, `test.${fileType}`)));
82 | }
83 | }
84 | });
85 |
86 | it('should organize files with source and output folder', () => {
87 | syncExec(`organize -s ${SOURCE_FOLDER} -o ${OUTPUT_FOLDER}`);
88 |
89 | for (let folderType of Object.keys(formats)) {
90 | for (let fileType of formats[folderType]) {
91 | fileType = fileType.toLowerCase();
92 | assert(fs.existsSync(path.join(OUTPUT_FOLDER, folderType, `test.${fileType}`)));
93 | }
94 | }
95 | });
96 |
97 | it('should only list moving commands and not actually move the files', () => {
98 | syncExec(`organize -s ${SOURCE_FOLDER} -l`);
99 |
100 | for (let folderType of Object.keys(formats)) {
101 | for (let fileType of formats[folderType]) {
102 | fileType = fileType.toLowerCase();
103 | assert(!fs.existsSync(path.join(OUTPUT_FOLDER, folderType, `test.${fileType}`)));
104 | }
105 | }
106 | });
107 |
108 | it('should organize files with specific file type', () => {
109 | syncExec(`organize -s ${SOURCE_FOLDER} -o ${OUTPUT_FOLDER} -t ai 3gp -f misc`);
110 |
111 | assert(fs.existsSync(path.join(OUTPUT_FOLDER, 'misc', 'test.ai')));
112 | assert(fs.existsSync(path.join(OUTPUT_FOLDER, 'misc', 'test.3gp')));
113 | });
114 |
115 | it('should organize files by dates', () => {
116 | syncExec(`organize -s ${SOURCE_FOLDER} -o ${OUTPUT_FOLDER} -d`);
117 |
118 | assert(fs.existsSync(path.join(OUTPUT_FOLDER, '2017-07-06', 'test.ai')));
119 | assert(fs.existsSync(path.join(OUTPUT_FOLDER, '2017-07-07', 'test.log')));
120 | assert(fs.existsSync(path.join(OUTPUT_FOLDER, '2017-07-09', 'test')));
121 | assert(fs.existsSync(path.join(OUTPUT_FOLDER, '2017-07-10', 'test.apib')));
122 | });
123 | });
124 |
--------------------------------------------------------------------------------