import Promise from 'bluebird';
import path from 'path';
import fileGlob from 'minimatch';
import fsp from './fsp';
import filelock from './lock';
function joinWith(dir) {
return (file) => {
return path.join(dir, file);
};
}
/**
* @class
*/
class File {
constructor(pathname) {
this._dir = process.cwd();
this._pathname = pathname;
}
_getStatsSync() {
return fsp.statSync(this._pathname);
}
_getStats() {
return fsp.statAsync(this._pathname);
}
_isHiddenFile() {
return (/^\./).test(path.basename(this._pathname));
}
_isHiddenDirectory() {
return (/(^|\/)\.[^\/\.]/g).test(this._pathname);
}
_depth(pathname) {
return pathname.split(path.sep).length - 1;
}
_access(permission) {
let hasPermission = true;
return fsp.accessAsync(this._pathname, permission)
.catch(() => hasPermission = false)
.then(() => hasPermission);
}
_checkAsyncStats(type) {
return this._getStats().then((stats) => stats[type]());
}
/**
* Synchronously determine if pathname is a directory
*
* @instance
* @memberOf File
* @method
* isDirectorySync
* @return boolean
* @example
* import File from 'file-js';
*
* const file = File.create('myDirectory');
* if (file.isDirectorySync()) {
* console.log('processing directory');
* }
*/
isDirectorySync() {
return this._getStatsSync().isDirectory();
}
/**
* Synchronously determine if pathname is a socket
*
* @instance
* @memberOf File
* @method
* isSocketSync
* @return boolean
* @example
* import File from 'file-js';
*
* const file = File.create('mysocket');
* if (file.isSocketSync()) {
* console.log('processing socket');
* }
*/
isSocketSync() {
return this._getStatsSync().isSocket();
}
/**
* Synchronously determine if pathname is a file
*
* @instance
* @memberOf File
* @method
* isFileSync
* @return boolean
* @example
* import File from 'file-js';
*
* const file = File.create('myDirectory');
* if (file.isFileSync()) {
* console.log('processing file');
* }
*/
isFileSync() {
return this._getStatsSync().isFile();
}
/**
* Determine if pathname is a directory
*
* @instance
* @memberOf File
* @method
* isDirectory
* @return If the Promise fulfils, the fulfilment value is
* a boolean indicating if the pathname is a directory
* @example
* import File from 'file-js';
*
* const file = File.create('myDirectory');
* file.isDirectory((isDirectory) => {
* console.log(isDirectory);
* });
*
*/
isDirectory() {
return this._checkAsyncStats('isDirectory');
}
/**
* Determine if pathname is a Socket
*
* @instance
* @memberOf File
* @method
* isSocket
* @return If the Promise fulfils, the fulfilment value is
* a boolean indicating if the pathname is a Socket
* @example
* import File from 'file-js';
*
* const file = File.create('mySocket');
* file.isSocket((isSocket) => {
* console.log(isSocket);
* });
*
*/
isSocket() {
return this._checkAsyncStats('isSocket');
}
/**
* Determine if pathname is a file
*
* @instance
* @memberOf File
* @method
* isDirectory
* @return If the Promise fulfils, the fulfilment value is
* a boolean indicating if the pathname is a file
* @example
* import File from 'file-js';
*
* const file = File.create('myDirectory');
* file.isFile((isFile) => {
* console.log(isFile);
* });
*/
isFile() {
return this._checkAsyncStats('isFile');
}
/**
* Synchronously determine if pathname is a hidden file
*
* @instance
* @memberOf File
* @method
* isHiddenSync
* @return boolean
* @example
* import File from 'file-js';
*
* const file = File.create('./myHiddenFile');
* if (file.isHiddenSync()) {
* console.log('processing hidden file');
* }
*/
isHiddenSync() {
if (!this.isDirectorySync()) {
return this._isHiddenFile();
}
return this._isHiddenDirectory();
}
/**
* Determine if pathname is a file
*
* @instance
* @memberOf File
* @method
* isDirectory
* @return If the Promise fulfils, the fulfilment value is
* a boolean indicating if the pathname is a file
* @example
* import File from 'file-js';
*
* const file = File.create('myDirectory');
* file.isFile((isFile) => {
* console.log(isFile);
* });
*/
isHidden() {
this.isDirectory()
.then((isDirectory) => {
if (!isDirectory) {
return this._isHiddenFile();
}
return this._isHiddenDirectory();
});
}
/**
* Renames the abstract pathname
*
* @instance
* @memberOf File
* @param {string|File} pathname - pathname either as a string or File instance
* @method
* rename
* @return If the Promise fulfils, the fulfilment value is undefined
* @example
* import File from 'file-js';
*
* const original = File.create('fileA');
* const renameTo = File.create('fileB');
* original
* .rename(renameTo)
* .then(() => {
* console.log(original.getName()) // prints fileA
* });
*/
rename(pathname) {
const newname = pathname instanceof File ? pathname.getName() : pathname;
return fsp
.renameAsync(this._pathname, newname)
.then(() => {
this._pathname = newname;
});
}
/**
* Synchronously get list of files, if pathname is a directory
*
* @instance
* @memberOf File
* @method
* getListSync
* @return array of files
* @example
* import File from 'file-js';
*
* const file = File.create('./myHiddenFile');
* const files = file.getListSync();
* console.log(files);
*/
getListSync() {
if (this.isDirectorySync()) {
return fsp.readdirSync(this._pathname).map((file) => {
return path.join(this._pathname, file);
});
}
return null;
}
/**
* Get list of file objects, if pathname is a directory
*
* @instance
* @memberOf File
* @method
* getList
* @param {string=} glob - file glob
* @return a promise. If the Promise fulfils, the fulfilment value is
* a list of pathnames
* @example
* import File from 'file-js';
*
* // get all json files
* const file = File.create('./myDirectory');
* file.getFiles('*.json')
* .then((jsonFiles) => {
* console.log(jsonFiles);
* });
*/
getList(glob) {
return this.getFiles(glob)
.then((list) => {
if (!list) return [];
return list.map((pathname) => pathname.getName());
});
}
/**
* Get list of file objects, if pathname is a directory
*
* @instance
* @memberOf File
* @param {string=} glob - file glob
* @method
* getFiles
* @return a promise. If the Promise fulfils, the fulfilment value is
* a list of File objects
* @example
* import File from 'file-js';
*
* // get last modified time of all json files
* const file = File.create('./myDirectory');
* file.getFiles('*.json')
* .then((jsonFiles) => {
* console.log(jsonFiles.map(file => file.lastModifiedSync()));
* });
*/
getFiles(glob) {
if (!this.isDirectory()) return Promise.resolve(null);
const results = fsp
.readdirAsync(this._pathname)
.map(joinWith(this._pathname))
.then((list) => {
if (!list) return Promise.resolve(null);
return list.map((pathname) => File.create(pathname));
});
if (glob) return results.filter((file) => file.isMatch(glob));
return results;
}
/**
* Synchronously get list of file objects, if pathname is a directory
*
* @instance
* @memberOf File
* @method
* getFileSync
* @return array of files
* @example
* import File from 'file-js';
*
* const file = File.create('./myHiddenFile');
* const files = file.getFileSync();
* console.log(files);
*/
getFilesSync(glob) {
if (this.isDirectorySync()) {
const files = this.getListSync()
.map((pathname) => {
return File.create(pathname);
});
if (glob) return files.filter((file) => file.isMatch(glob));
return files;
}
return null;
}
/**
* Synchronously caculate the depth of a directory
*
* @instance
* @memberOf File
* @method
* getDepthSync
* @return boolean
* @example
* import File from 'file-js';
*
* const file = File.create('myDirectory');
* console.log(file.getDepthSync());
*/
getDepthSync() {
if (!this.isDirectorySync()) {
return this._depth(path.dirname(this._pathname));
}
return this._depth(this._pathname);
}
/**
* Returns the pathname as a string
*
* @instance
* @memberOf File
* @method
* getName
* @return String
* @example
* import File from 'file-js';
*
* const file = File.create('myDirectory');
* console.log(file.getName());
*/
getName() {
return this._pathname;
}
/**
* Returns the absolutePath
*
* @instance
* @memberOf File
* @method
* getAbsolutePath
* @return String
* @example
* import File from 'file-js';
*
* const file = File.create('myFile');
* console.log(file.getAbsolutePath());
*/
getAbsolutePath() {
if (path.isAbsolute(this._pathname)) {
return this._pathname;
}
return [this._dir, this._pathname].join(path.sep);
}
/**
* Returns the canonical path
*
* @instance
* @memberOf File
* @method
* getCanonicalPath
* @return String
* @example
* import File from 'file-js';
*
* const file = File.create('myFile');
* console.log(file.getCanonicalPath());
*/
getCanonicalPath() {
return path.normalize(this.getAbsolutePath());
}
/**
* Returns the file extension.
*
* @instance
* @memberOf File
* @method
* getPathExtension
* @return String
* @example
* import File from 'file-js';
*
* const file = File.create('./tmp.sh');
* console.log(file.getPathExtension()); // sh
*/
getPathExtension() {
return path.extname(this._pathname).substring(1);
}
isMatch(globPattern) {
const glob = new fileGlob.Minimatch(globPattern, {
matchBase: true
});
return glob.match(this._pathname);
}
lastModifiedSync() {
return this._getStatsSync()['mtime'];
}
lastAccessedSync() {
return this._getStatsSync()['atime'];
}
lastChangedSync() {
return this._getStatsSync()['ctime'];
}
sizeSync() {
return this._getStatsSync().size;
}
isWritable() {
return this._access(fsp.W_OK);
}
isReadable() {
return this._access(fsp.R_OK);
}
isExecutable() {
return this._access(fsp.X_OK);
}
delete() {
return fsp.unlinkAsync(this._pathname);
}
/**
* Locks the pathname
*
* @instance
* @memberOf File
* @method
* withLock
* @return returning value of function
* @example
* import File from 'file-js';
*
* const file = File.create('myFile');
* file.with(() => {
* if (file.isFileSync()) {
* file.delete();
* }
* });
*/
withLock(fn) {
return filelock.lockAsync(this._pathname)
.then(() => {
return fn();
})
.finally(() => {
filelock.unlockAsync(this._pathname);
});
}
/**
* Static factory method to create an instance of File
*
* @static
* @memberOf File
* @method
* create
* @return File instance
* @example
* import File from 'file-js';
*
* const file = File.create();
*/
static create(filename) {
return new File(filename);
}
}
module.exports.create = File.create;