Skip to content

Commit

Permalink
feat: init
Browse files Browse the repository at this point in the history
  • Loading branch information
likun7981 committed Dec 2, 2020
0 parents commit 1e84c6e
Show file tree
Hide file tree
Showing 17 changed files with 322 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.yml]
indent_style = space
indent_size = 2
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
yarn.lock
destDir
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: node_js
node_js:
- '12'
- '10'
- '8'
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"hlink"
]
}
41 changes: 41 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env node
"use strict";
const meow = require("meow");
const hardLink = require("./lib/index");

const cli = meow(
`
Usage
$ hlink [source] [dist]
Options
--saveLevel,-l [Default: 1]
说明:如果源视频文件目录结构是 /share/download/movie/a.mkv,硬链接目的地目录是/share/media
saveLevel=2 时 结果就是 "/share/media/download/movie/a.mkv"
saveLevel=1 时 结果就是 "/share/media/movie/a.mkv"
--ext,-e [Default: mkv,mp4,rmvb]
--maxFindLevel [Default: 4]
`,
{
flags: {
saveLevel: {
type: "string",
default: "1",
alias: "s"
},
ext: {
type: "string",
default: "mkv,mp4,rmvb",
alias: "e"
},
maxFindLevel: {
type: "string",
default: "4",
alias: "m"
}
}
}
);

hardLink(cli.input, cli.flags);
114 changes: 114 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const fs = require("fs-extra");
const path = require("path");
const warning = require("./warning");
const execa = require("execa");
const log = require("./log");
const chalk = require("chalk");

const resolvePath = path.resolve;

function checkLevels(levels) {
warning(Number.isNaN(levels), "保存的最大层级saveDirLevel必须设置为数字");
warning(
levels > 2 || levels < 0,
"保存的最大层级saveDirLevel只能设置为0/1/2"
);
}
function checkFindLevels(levels) {
warning(Number.isNaN(levels), "查找的最大层级maxFindLevel必须设置为数字");
warning(levels > 6 || levels < 1, "保存的最大层级maxFindLevel不能小于1大于6");
}

function checkDirectory(source, dest) {
fs.ensureDirSync(dest);
warning(source === dest, "起始地址和目标地址不能相同");
}

function getRealDestPath(sourcePath, destPath, saveLevels, startPath) {
if (saveLevels > 0) {
return path.resolve(
destPath,
path
.relative(startPath, path.resolve(sourcePath))
.split(path.sep)
.slice(-saveLevels)
.join(path.sep)
);
}
return destPath;
}

function hardLink(input, options) {
warning(!input.length, "必须指定目标地址");
let source = resolvePath(process.cwd());
let dest = false;
if (input.length === 1) {
dest = resolvePath(input[0]);
} else if (input.length === 2) {
source = resolvePath(input[0]);
dest = resolvePath(input[1]);
}
checkDirectory(source, dest);
const { s, e, m } = options;
const exts = e.split(",");
const saveLevels = +s;
const maxFindLevels = +m;
checkLevels(saveLevels);
checkFindLevels(maxFindLevels);
const messageMap = {
e: " 包含的后缀有:",
m: " 源地址最大查找层级为:",
s: " 硬链保存的最大目录层级为:"
};
log.info("开始创建硬链...");
log.info("当前配置为:");
Object.keys(messageMap).forEach(k => {
log.info(`${messageMap[k]}${chalk.cyanBright(options[k])}`);
});
function start(sourcePath, currentLevel = 1) {
if (currentLevel > maxFindLevels) {
return;
}
const sourceDirContent = fs.readdirSync(sourcePath);
sourceDirContent.forEach(s => {
const extname = path.extname(s).replace(".", "");
const filePath = resolvePath(sourcePath, s);
if (fs.lstatSync(filePath).isDirectory() && !s.startsWith(".")) {
// 地址继续循环
start(filePath, currentLevel + 1);
} else if (exts.indexOf(extname) > -1) {
// 做硬链接
const realDestPath = getRealDestPath(
sourcePath,
dest,
saveLevels,
source
);
const sourceNameForMessage = chalk.yellow(
path.relative(path.join(source, ".."), filePath)
);
const destNameForMessage = chalk.cyan(
path.relative(path.join(dest, ".."), path.join(realDestPath, s))
);
try {
fs.ensureDirSync(realDestPath);
execa.sync("ln", [filePath, realDestPath]);
log.success(
`源地址 "${sourceNameForMessage}" 硬链成功, 硬链地址为 "${destNameForMessage}" `
);
} catch (e) {
if (e.stderr.indexOf("File exists") === -1) {
console.log(e);
} else {
log.warn(
`目标地址 "${destNameForMessage}" 文件已存在, 跳过源地址 "${sourceNameForMessage}" 硬链接创建`
);
}
}
}
});
}
start(source);
}

module.exports = hardLink;
18 changes: 18 additions & 0 deletions lib/log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const chalk = require("chalk");
const logHead = "HLINK";
const log = {
info: function(...args) {
console.log(chalk.cyan(`[${logHead} INFO]:`), ...args);
},
warn: function(...args) {
console.log(chalk.yellow(`[${logHead} WARN]:`), ...args);
},
error: function(...args) {
console.log(chalk.red(`[${logHead} ERROR]:`), ...args);
},
success: function(...args) {
console.log(chalk.green(`[${logHead} SUCCESS]:`), ...args);
}
};

module.exports = log;
7 changes: 7 additions & 0 deletions lib/warning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const log = require("./log");
module.exports = (warning, message) => {
if (warning) {
log.warn(message);
process.exit(0);
}
};
9 changes: 9 additions & 0 deletions license
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
MIT License

Copyright (c) YOUR NAME <YOUR EMAIL> (YOUR WEBSITE)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
42 changes: 42 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "unicorn-fun-cli",
"version": "0.0.0",
"description": "My awesome command-line tool",
"license": "MIT",
"repository": "YOUR-GITHUB-USERNAME/unicorn-fun-cli",
"author": {
"name": "YOUR NAME",
"email": "YOUR EMAIL",
"url": "YOUR WEBSITE"
},
"bin": {
"hlink": "cli.js"
},
"engines": {
"node": ">=8"
},
"scripts": {
"test": "xo && ava",
"start": "./cli.js ./sourceDir ./destDir"
},
"files": [
"cli.js"
],
"keywords": [
"cli",
"cli-app",
"unicorn",
"fun"
],
"dependencies": {
"chalk": "^4.1.0",
"fs-extra": "^9.0.1",
"meow": "^5.0.0",
"unicorn-fun": "^0.0.0"
},
"devDependencies": {
"ava": "^2.1.0",
"execa": "^2.1.0",
"xo": "^0.24.0"
}
}
57 changes: 57 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# node-cli-boilerplate

> Boilerplate to kickstart creating a Node.js command-line tool
This is what I use for [my own command-line tools](https://www.npmjs.com/~sindresorhus).

Also check out [`node-module-boilerplate`](https://github.com/sindresorhus/node-module-boilerplate).

## Getting started

**Click the "Use this template" button.**

Alternatively, create a new directory and then run:

```
$ curl -fsSL https://github.com/sindresorhus/node-cli-boilerplate/archive/master.tar.gz | tar -xz --strip-components=1
```

There's also a [Yeoman generator](https://github.com/sindresorhus/generator-nm).


---

**Remove everything from here and above**

---


# unicorn-fun-cli [![Build Status](https://travis-ci.org/YOUR-GITHUB-USERNAME/unicorn-fun-cli.svg?branch=master)](https://travis-ci.org/YOUR-GITHUB-USERNAME/unicorn-fun-cli)

> My awesome command-line tool

## Install

```
$ npm install --global unicorn-fun-cli
```


## Usage

```
$ unicorn-fun --help
Usage
$ unicorn-fun [input]
Options
--postfix Lorem ipsum [Default: rainbows]
Examples
$ cli-name
unicorns & rainbows
$ cli-name ponies
ponies & rainbows
```
Empty file added sourceDir/a.mkv
Empty file.
Empty file added sourceDir/dir1/b.mkv
Empty file.
Empty file added sourceDir/dir1/dir2/c.mkv
Empty file.
7 changes: 7 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import test from 'ava';
import execa from 'execa';

test('main', async t => {
const {stdout} = await execa('./cli.js', ['ponies']);
t.is(stdout, 'ponies & rainbows');
});

0 comments on commit 1e84c6e

Please sign in to comment.