first commit
This commit is contained in:
+44
@@ -0,0 +1,44 @@
|
||||
# document
|
||||
いわゆる製作メモ。
|
||||
|
||||
## TODO
|
||||
* 分割ファイル読み込み
|
||||
- 対応unrarモジュール待ち。
|
||||
* 内部でStream
|
||||
- 同上。
|
||||
|
||||
|
||||
## testディレクトリ
|
||||
- index.mjs
|
||||
- テストで実行するモジュール。
|
||||
- contents/
|
||||
- テストに使う書庫ファイル置き場。
|
||||
- example.rar
|
||||
- example-encrypted.rar
|
||||
- パスワードはそのままpassword.
|
||||
- CP932.rar
|
||||
- WinRARv5.31で作った日本語名のディレクトリ・ファイルを含む書庫。
|
||||
- 現在はUTF-8にパスが変換された上で圧縮されるため、実際にはCP932ではない。
|
||||
|
||||
## Mod
|
||||
|
||||
### dependencies
|
||||
* @honeo/check
|
||||
- 型チェックなど。
|
||||
* console-wrapper
|
||||
- コンソール一括ON/OFF
|
||||
* fs-extra
|
||||
- fs機能拡張版。
|
||||
* node-unrar-js
|
||||
- unrar.
|
||||
* sanitize-filename
|
||||
- 出力ファイルパス正規化。
|
||||
|
||||
|
||||
### devDependencies
|
||||
* @honeo/test
|
||||
* テスト。
|
||||
|
||||
|
||||
## 参考
|
||||
* [RAR - Wikipedia](https://ja.wikipedia.org/wiki/RAR)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Mod
|
||||
import _unrar from './lib/unrar.mjs';
|
||||
import _list from './lib/list.mjs';
|
||||
|
||||
|
||||
export const unrar = _unrar;
|
||||
export const list = _list;
|
||||
|
||||
export default {
|
||||
unrar,
|
||||
list
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// Mod
|
||||
import console from 'console-wrapper';
|
||||
import fse from "fs-extra";
|
||||
import path from 'path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import Unrar from 'node-unrar-js';
|
||||
import {is, not, any} from '@honeo/check';
|
||||
|
||||
// Var
|
||||
const obj_defaultOp = {}
|
||||
|
||||
/*
|
||||
コンテンツ一覧を配列で返す。
|
||||
|
||||
引数
|
||||
1: string
|
||||
入力する.rarファイルのパス
|
||||
返り値
|
||||
array
|
||||
*/
|
||||
async function list(input, _options={}){
|
||||
console.group('list()');
|
||||
console.log(input);
|
||||
console.log(_options);
|
||||
|
||||
// validation
|
||||
if( is.false(
|
||||
is.str(input),
|
||||
is.arrbuf(input)
|
||||
) ){
|
||||
throw new TypeError(`Invalid arguments 1: not string of arraybuffer`);
|
||||
}
|
||||
if( not.obj(_options) ){
|
||||
throw new TypeError(`Invalid arguments 2: not object`);
|
||||
}
|
||||
|
||||
// var
|
||||
const options = {...obj_defaultOp, ..._options}
|
||||
const arrbuf = await (async function(){
|
||||
if( is.str(input) ){
|
||||
console.log('input: path');
|
||||
return Uint8Array.from(
|
||||
await fse.readFile(input)
|
||||
).buffer;
|
||||
}else{
|
||||
console.log('input: arraybuffer');
|
||||
return input;
|
||||
}
|
||||
}());
|
||||
const extractor = await Unrar.createExtractorFromData({
|
||||
data: arrbuf,
|
||||
password: options.password
|
||||
});
|
||||
|
||||
const {arcHeader, fileHeaders} = extractor.getFileList();
|
||||
const arr_contents = [];
|
||||
let count = 0;
|
||||
for(let fileHeader of fileHeaders){
|
||||
// 正規化
|
||||
const {dir, base} = path.parse(fileHeader.name);
|
||||
const base_sanitized = sanitize(base);
|
||||
const str_contentPath = path.normalize(
|
||||
path.join(dir, base_sanitized)
|
||||
);
|
||||
const str_contentType = fileHeader.flags.directory ?
|
||||
'directory':
|
||||
'file';
|
||||
const obj_content = {
|
||||
path: str_contentPath,
|
||||
size: fileHeader.packSize,
|
||||
type: str_contentType
|
||||
}
|
||||
console.log(++count, obj_content);
|
||||
arr_contents.push(obj_content);
|
||||
}
|
||||
arr_contents.reverse(); // 直感的にする
|
||||
console.log('result', arr_contents);
|
||||
console.groupEnd();
|
||||
return arr_contents;
|
||||
}
|
||||
|
||||
export default list;
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
// Mod: npm
|
||||
import console from 'console-wrapper';
|
||||
import fse from "fs-extra";
|
||||
import path from 'path';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import Unrar from 'node-unrar-js';
|
||||
import {is, not, any} from '@honeo/check';
|
||||
// Local
|
||||
import list from './list.mjs';
|
||||
|
||||
// Var
|
||||
const obj_defaultOp = {
|
||||
filter: null,
|
||||
overwrite: false,
|
||||
sanitize: true
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
コンテンツを指定して展開する
|
||||
|
||||
引数
|
||||
1: string
|
||||
入力する.rar書庫のパス。
|
||||
2: string
|
||||
出力するディレクトリのパス。
|
||||
返り値
|
||||
promise
|
||||
出力先ディレクトリのパス文字列を引数に解決する。
|
||||
*/
|
||||
async function unrar(input, _output, _options={}){
|
||||
console.group('unrar()');
|
||||
console.log(_output);
|
||||
console.log(_options);
|
||||
|
||||
// validation
|
||||
if( is.false(
|
||||
is.str(input),
|
||||
is.arrbuf(input)
|
||||
) ){
|
||||
throw new TypeError(`Invalid arguments 1: not string of arraybuffer`);
|
||||
}
|
||||
if( not.str(_output) ){
|
||||
throw new TypeError(`Invalid arguments 2: not string`);
|
||||
}
|
||||
if( not.obj(_options) ){
|
||||
throw new TypeError(`Invalid arguments 3: not object`);
|
||||
}
|
||||
|
||||
// var
|
||||
const output = path.resolve(_output); // 出力先Dirのフルパス化
|
||||
const options = {...obj_defaultOp, ..._options}
|
||||
const arrbuf = await (async function(){
|
||||
if( is.str(input) ){
|
||||
console.log('input: path');
|
||||
return Uint8Array.from(
|
||||
await fse.readFile(input)
|
||||
).buffer;
|
||||
}else{
|
||||
console.log('input: arraybuffer');
|
||||
return input;
|
||||
}
|
||||
}());
|
||||
const isFilter = is.func(options.filter);
|
||||
|
||||
const extractor = await Unrar.createExtractorFromData({
|
||||
data: arrbuf,
|
||||
password: options.password
|
||||
});
|
||||
const {arcHeader, files} = extractor.extract({});
|
||||
|
||||
let count = 0;
|
||||
for(let {fileHeader, extraction} of files){
|
||||
const isDir = fileHeader.flags.directory;
|
||||
console.log(++count, `${isDir?'directory':'file'}: ${fileHeader.name}`);
|
||||
// 正規化
|
||||
if( options.sanitize ){
|
||||
const {dir, base} = path.parse(fileHeader.name);
|
||||
const base_sanitized = sanitize(base);
|
||||
fileHeader.name = path.normalize(path.join(
|
||||
dir,
|
||||
base_sanitized
|
||||
));
|
||||
}
|
||||
const str_outputContentFullpath = path.join(output, fileHeader.name);
|
||||
|
||||
// options.filter
|
||||
if( isFilter ){
|
||||
const type = isDir ?
|
||||
'directory': 'file';
|
||||
const str_contentPath = fileHeader.name;
|
||||
const isSkip = options.filter({
|
||||
type,
|
||||
path: str_contentPath,
|
||||
size: fileHeader.packSize
|
||||
})===false;
|
||||
console.log('options.filster:', isSkip);
|
||||
if( isSkip ){
|
||||
continue;
|
||||
}
|
||||
}else{
|
||||
console.log('options.filter: not function');
|
||||
}
|
||||
|
||||
// dirなら作成
|
||||
if( isDir ){
|
||||
console.log(`ensure: ${str_outputContentFullpath}`);
|
||||
await fse.ensureDir(str_outputContentFullpath);
|
||||
}else{
|
||||
// ファイルなら存在確認、あれば上書き許可を確認
|
||||
const isAlreadyExists = await fse.existsSync(str_outputContentFullpath);
|
||||
if( !options.overwrite && isAlreadyExists ){
|
||||
console.log(`skip: ${str_outputContentFullpath}`);
|
||||
continue;
|
||||
}else if( isAlreadyExists && options.overwrite ){
|
||||
console.log(`overwrite: ${str_outputContentFullpath}`);
|
||||
}else{
|
||||
console.log(`write: ${str_outputContentFullpath}`);
|
||||
}
|
||||
await fse.outputFile(str_outputContentFullpath, extraction);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`result: ${output}`);
|
||||
console.groupEnd();
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
export default unrar;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "unrar-promise",
|
||||
"version": "3.1.0",
|
||||
"description": "かんたん.rar展開モジュール",
|
||||
"main": "index.mjs",
|
||||
"scripts": {
|
||||
"test": "node ./test/index.mjs"
|
||||
},
|
||||
"author": "honeo",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@honeo/check": "^2.6.0",
|
||||
"console-wrapper": "^1.1.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"node-unrar-js": "^2.0.0",
|
||||
"sanitize-filename": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@honeo/test": "^3.3.0"
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "test"
|
||||
},
|
||||
"keywords": [
|
||||
"rar",
|
||||
"unrar",
|
||||
"promise"
|
||||
]
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
# unrar-promise
|
||||
* [honeo/unrar-promise](https://github.com/honeo/unrar-promise)
|
||||
* [unrar-promise](https://www.npmjs.com/package/unrar-promise)
|
||||
|
||||
## なにこれ
|
||||
かんたん.rar展開モジュール。
|
||||
|
||||
## 使い方
|
||||
```sh
|
||||
$ npm i unrar-promise
|
||||
```
|
||||
```js
|
||||
import {unrar, list} from 'unrar-promise';
|
||||
|
||||
await unrar('archive.rar', './output');
|
||||
```
|
||||
|
||||
## API
|
||||
* 出力先について
|
||||
- ファイルが既にあればスキップする。
|
||||
- ディレクトリがなければ作成する。
|
||||
|
||||
### options
|
||||
| key | type | default | description |
|
||||
|:--------- |:-------- | ------- | --------------------------------------------------------------------- |
|
||||
| filter | function | | 出力するコンテンツ毎にobjectを引数に実行され、falseが返ればskipする。 |
|
||||
| overwrite | boolean | false | 上書きを許可するか。 |
|
||||
| password | string | | 書庫のパスワード。 |
|
||||
| sanitize | boolean | true | ファイル名を[node-sanitize-filename](https://github.com/parshap/node-sanitize-filename)で正規化するか。 |
|
||||
|
||||
|
||||
### unrar(input, outputDir [, options])
|
||||
引数1パスの.rar書庫を引数2のディレクトリへ展開する。
|
||||
展開先ディレクトリのパスを引数に解決するpromiseを返す。
|
||||
```js
|
||||
// .rar path => "output"
|
||||
const dirPath = await unrar('hoge.rar', 'output');
|
||||
|
||||
// or Buffer<.rar>
|
||||
const dirPath = await unrar(arraybuffer, 'output');
|
||||
|
||||
// options
|
||||
const dirPath = await unrar('hoge.rar', 'output', {
|
||||
filter({path, type, size}){
|
||||
return type==='file' && /\.txt$/.test(path); // *.txt file only
|
||||
},
|
||||
overwrite: true,
|
||||
password: '123456'
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### list(input [, options])
|
||||
引数1パスの.rar書庫が持つコンテンツ一覧をpromise<[...object]>で取得する。
|
||||
```js
|
||||
const arr = await list('foobar.rar');
|
||||
|
||||
// or Buffer<.rar>
|
||||
const arr = await list(arraybuffer);
|
||||
|
||||
// example result
|
||||
[{
|
||||
path: 'foo',
|
||||
size: 0,
|
||||
type: 'directory'
|
||||
}, {
|
||||
path: 'foo/bar.txt',
|
||||
size: 8,
|
||||
type: 'file',
|
||||
}]
|
||||
|
||||
// options
|
||||
const arr = await list('foobar.rar', {
|
||||
password: 'qwerty'
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### v2 => v3
|
||||
* CJS => ESM.
|
||||
* unrar()
|
||||
- options.filterに渡されるobject.typeが全て小文字になった。
|
||||
- "File" => "file"
|
||||
- options.filterに渡されるobject.pathが末尾に"/"を含まなくなった。
|
||||
- "foo/" => "foo"
|
||||
* list()
|
||||
- 返り値を[...string]から[...object]に変更。
|
||||
- 返り値のpathが末尾に"/"を含まなくなった。
|
||||
- "foo/" => "foo"
|
||||
|
||||
### v1 => v2
|
||||
* .extract(), extractAll()
|
||||
- 廃止して.unrar()に統合。
|
||||
* .list()
|
||||
- 引数2をstringからobjectに変更。
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+175
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
Test
|
||||
テスト用の書庫ファイル ./example.rar をコピーして使う。
|
||||
example-encrypted.rarは暗号化されているだけで中身は同じ。
|
||||
|
||||
example.rar [
|
||||
example [
|
||||
hoge.txt,
|
||||
foo [
|
||||
bar.txt
|
||||
],
|
||||
empty []
|
||||
]
|
||||
]
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// Modules
|
||||
// import console from 'console-wrapper';
|
||||
import Test from '@honeo/test';
|
||||
import {unrar, list} from '../index.mjs';
|
||||
import path from 'path';
|
||||
import fse from 'fs-extra';
|
||||
import {is, not, any} from '@honeo/check';
|
||||
|
||||
// Var
|
||||
const obj_options = {
|
||||
chtmpdir: true,
|
||||
console: true,
|
||||
exit: true,
|
||||
tmpdirOrigin: './test/contents'
|
||||
}
|
||||
|
||||
// console.enable();
|
||||
|
||||
|
||||
Test([
|
||||
|
||||
async function(){
|
||||
console.log('unrar(rar, cwd)');
|
||||
const dirPath = await unrar('example.rar', './');
|
||||
|
||||
return is.true(
|
||||
dirPath===process.cwd(),
|
||||
await fse.exists('example'),
|
||||
await fse.exists('example/hoge.txt')
|
||||
);
|
||||
},
|
||||
|
||||
async function(){
|
||||
console.log('unrar(buf, cwd)');
|
||||
const arraybuffer = Uint8Array.from(
|
||||
await fse.readFile('example.rar')
|
||||
).buffer;
|
||||
const dirPath = await unrar(arraybuffer, './');
|
||||
return is.true(
|
||||
dirPath===process.cwd(),
|
||||
await fse.exists('example'),
|
||||
await fse.exists('example/hoge.txt')
|
||||
);
|
||||
},
|
||||
|
||||
async function(){
|
||||
console.log('unrar(rar, NotExistDir)');
|
||||
const dirPath = await unrar('example.rar', 'output');
|
||||
console.log(
|
||||
);
|
||||
return is.true(
|
||||
dirPath===path.join(process.cwd(), 'output'),
|
||||
await fse.exists('./output/example/hoge.txt')
|
||||
);
|
||||
},
|
||||
|
||||
async function(){
|
||||
console.log('unrar(rar, cwd) - case overwrite skip');
|
||||
await unrar('example.rar', './');
|
||||
const stats_before = await fse.stat('example/hoge.txt');
|
||||
await unrar('example.rar', './');
|
||||
const stats_after = await fse.stat('example/hoge.txt');
|
||||
return stats_before.atimeMs===stats_after.atimeMs;
|
||||
},
|
||||
|
||||
// Atom内臓Node.js(v14)だと何故かコケる
|
||||
async function(){
|
||||
console.log('unrar(rar, cwd, {overwrite: true})');
|
||||
await unrar('example.rar', './');
|
||||
const stats_before = await fse.stat('example/hoge.txt');
|
||||
await unrar('example.rar', './', {overwrite: true});
|
||||
const stats_after = await fse.stat('example/hoge.txt');
|
||||
return stats_before.atimeMs!==stats_after.atimeMs;
|
||||
},
|
||||
|
||||
|
||||
async function(){
|
||||
console.log('unrar(rar, cwd, {filter(){}}) - through');
|
||||
let count = 0;
|
||||
await unrar('example.rar', './', {
|
||||
filter({path, type}){
|
||||
count++;
|
||||
if( !is.str(path, type) ){
|
||||
throw new Error('filter');
|
||||
}
|
||||
}
|
||||
});
|
||||
return count===5
|
||||
},
|
||||
|
||||
|
||||
async function(){
|
||||
console.log('unrar(rar, cwd, {filter(){}}) - dir only');
|
||||
await unrar('example.rar', './', {
|
||||
filter({path, type}){
|
||||
return type==='directory';
|
||||
}
|
||||
});
|
||||
return is.false(
|
||||
await fse.exists('example/foo/bar.txt'),
|
||||
await fse.exists('example/hoge.txt')
|
||||
);
|
||||
},
|
||||
|
||||
async function(){
|
||||
console.log('unrar(rar-encrypted, cwd, {password})');
|
||||
const dirPath = await unrar(
|
||||
'example-encrypted.rar',
|
||||
'./',
|
||||
{password: 'password'}
|
||||
);
|
||||
return is.true(
|
||||
dirPath===process.cwd(),
|
||||
await fse.exists('example-encrypted/hoge.txt'),
|
||||
await fse.exists('example-encrypted/foo/bar.txt'),
|
||||
await fse.exists('example-encrypted/empty')
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
async function(){
|
||||
console.log('list(rar)');
|
||||
const arr = await list('example.rar');
|
||||
return arr.length===5;
|
||||
},
|
||||
|
||||
async function(){
|
||||
console.log('list(buf)');
|
||||
const arraybuffer = Uint8Array.from(
|
||||
await fse.readFile('example.rar')
|
||||
).buffer;
|
||||
const arr = await list(arraybuffer);
|
||||
return arr.length===5;
|
||||
},
|
||||
|
||||
async function(){
|
||||
console.log('list(rar-encrypted, {password}');
|
||||
const arr = await list(
|
||||
'example-encrypted.rar',
|
||||
{password: 'password'}
|
||||
);
|
||||
return arr.length===5;
|
||||
},
|
||||
|
||||
|
||||
async function(){
|
||||
console.log('list(rar) - 日本語パスを含む書庫');
|
||||
const arr = await list('CP932.rar');
|
||||
return is.true(
|
||||
is.arr(arr),
|
||||
arr.length===2,
|
||||
arr[0].path==='ディレクトリ',
|
||||
arr[1].path===path.normalize('ディレクトリ/テキストファイル.txt')
|
||||
);
|
||||
}
|
||||
|
||||
], obj_options);
|
||||
Reference in New Issue
Block a user