/**
* @fileOverview Manipulate project directories and files.
* @name index.js
* @author Dongsoo Lee <mrlee_23@naver.com>
* @copyright 2018 Dongsoo Lee <mrlee_23@naver.com>
* @module index
* @version 0.1.4
* @since 0.0.1
* @created 2017-12-26
*
* @requires module:./lib/typeCheck.js
* @requires module:./lib/path.js
*/
/**
* For matching file name.
* @typedef {string|RegExp|Array} Query
*/
/**
* Represent file system path, include relative and absolute paths.
* @typedef {string} Path
*/
/**
* Absolute path of real file system.
* @typedef {string} AbsPath
*/
/**
* Represent managed path in project, include relative and absolute paths.
* @typedef {string} ProjectPath
*/
/**
* Represent managed absolute path in project.
* @typedef {string} ProjectAbsPath
*/
const type = require('./lib/typeCheck.js'),
path = require('./lib/path.js');
/**
* @class
* @name ProjectDir
* @classdesc project-dir main class
* @version 0.1.4
* @since 0.0.1
* @created 2017-12-26
*/
class ProjectDir {
/**
* @public
* @instance
* @function constructor
* @version 0.1.4
* @since 0.0.1
* @created 2017-12-26
* @memberof module:index~ProjectDir
* @description Generating new project directory object.
*
* @param {Path} currentPath - Starting point for search base directory. This path must be an existing file or directory.
* @param {Query} basename - Dominating file or directory names.
*
* @example
* const myProject = new ProjectDir('./', 'node_modules')
*
* const myProject2 = new ProjectDir('./', '.git')
*
* const myProject3 = new ProjectDir('./', ['node_modules', '.git']) // has node_modules and .git directory
*/
constructor(currentPath, basename) {
(basename == null) && (this.basename = [".git", "package.json"]);
this.basename = basename;
this.basedir = currentPath;
}
/**
* @public
* @instance
* @function set_basename
* @version 0.1.4
* @since 0.0.1
* @created 2017-12-26
* @memberof module:index~ProjectDir
* @description Set dominating file or directory names.
*
* @param {Query} basename - Dominating file or directory names.
* @throws {Error} Not a Query type.
*
* @example
* myProject.basename = ['.git', 'package.json']
*/
set basename (basename) {
if (!type.isQuery(basename)) throw new Error(`basename(${basename}) is not a Query type.`);
this._basename = basename;
}
/**
* @public
* @instance
* @function get_basename
* @version 0.1.4
* @since 0.0.1
* @created 2018-01-22
* @memberof module:index~ProjectDir
* @description Get basename of current project
*
* @returns {Path} Current basenames.
*
* @example
* myProject.basename // => ['.git', 'package.json']
*/
get basename () { return this._basename; }
/**
* @public
* @instance
* @function set_basedir
* @version 0.1.4
* @since 0.0.1
* @created 2017-12-26
* @memberof module:index~ProjectDir
* @description Set project's base directory.
*
* @param {Path} _path - Strting point for search base directory.
* @throws {Error} Failed searching base directory of project.
*
* @example
* myProject.basedir = './abcd'; // => '/home/user/myProject'
* myProject.wd; // => '/home/user/myProject/abcd'
*
* myProject.basedir = './abcd/efgh'; // => '/home/user/myProject'
* myProject.wd; // => '/home/user/myProject/abcd/efgh'
*/
set basedir (_path) {
_path = path.resolve(_path);
type.isFile(_path) && (_path = path.dirname(_path));
if (!type.isDir(_path)) throw new Error(`_path(${_path}) does not exist directory.`);
if (type.isRoot(_path)) throw new Error(`_path(${_path}) cannot be a root.`);
let basedir;
if (Array.isArray(this.basename)) {
this.basename.some(name => {
let tmpLoc = path.locateBase(_path, name);
!type.isRoot(tmpLoc) && (basedir = tmpLoc);
});
} else {
basedir = path.locateBase(_path, this.basename);
}
if (type.isRoot(basedir) || basedir == null) throw new Error(`_path(${_path}) has no parent node name of basename(${this.basename})`);
this._basedir = basedir;
this.wd = this.retrieve(_path);
}
/**
* @public
* @instance
* @function get_basedir
* @version 0.1.4
* @since 0.0.1
* @created 2018-01-22
* @memberof module:index~ProjectDir
* @description Get base directory of current project.
*
* @returns {Path} Current base directory's absolute path.
*
* @example
* myProject.basedir // => '/home/user/myProject'
*/
get basedir () { return this._basedir; }
/**
* @public
* @instance
* @function set_wd
* @version 0.1.4
* @since 0.0.1
* @created 2017-12-26
* @memberof module:index~ProjectDir
* @description Set working directory based on current project's base directory.
*
* @param {ProjectPath} _path - Sub path of base directory.
* @throws {Error} Out of range in root path.
*
* @example
* // root
* myProject.wd = '/'; // => '/home/user/myProject'
*
* // relative path
* myProject.wd = 'abcd'; // => '/home/user/myProject/abcd'
* myProject.wd = 'efgh'; // => '/home/user/myProject/abcd/efgh'
* myProject.wd = '../'; // => '/home/user/myProject/abcd'
*
* myProject.wd = '/aaaa'; // => '/home/user/myProject/aaaa'
*/
set wd (_path) {
let wd = this.resolve(_path);
if (wd == null) throw new Error(`working directory path(${_path}) is out of range in root path.`);
wd != null && (this._workingDir = wd);
}
/**
* @public
* @instance
* @function get_wd
* @version 0.1.4
* @since 0.0.1
* @created 2018-01-22
* @memberof module:index~ProjectDir
* @description Get current working directory.
*
* @returns {Path} Current working directory's absolute path.
*
* @example
* myProject.wd // => '/home/user/myProject/abcd'
*/
get wd () { return this._workingDir || this.basedir; }
/**
* @public
* @instance
* @function resolve
* @version 0.1.4
* @since 0.0.1
* @created 2017-12-26
* @memberof module:index~ProjectDir
* @description Resolving the path based on current project's base directory.
*
* @param {ProjectPath} _path - to resolve path.
* @throws {Error} _path is not a string type.
* @returns {null|Path} resolved path or null out of range in root path.
*
* @example
* myProject.wd = '/';
* myProject.resolve('abcd'); // => '/home/user/myProject/abcd'
*
* myProject.wd = '/aaaa';
* myProject.resolve('bbbb'); // => '/home/user/myProject/aaaa/bbbb'
*
* myProject.wd = '/aaaa';
* myProject.resolve('/abcd'); // => '/home/user/myProject/abcd'
*/
resolve (_path) {
if (typeof _path !== 'string') throw new Error(`_path(${_path}) is not a string type.`);
if (path.isAbsolute(_path)) {
_path = path.resolve(this.basedir, _path.substr(1));
} else {
_path = path.resolve(this.wd || this.basedir, _path);
}
// out of range in project directory.
if (!path.equal(this.basedir, _path) &&
!path.isParent(this.basedir, _path))
return null;
return _path;
}
/**
* @public
* @instance
* @function retrieve
* @version 0.1.4
* @since 0.0.5
* @created 2017-12-27
* @memberof module:index~ProjectDir
* @description Retrieve project path from absolute real path.
*
* @param {AbsPath} _path - absolute real path.
* @throws {Error} If not a string type, If not an absolute path.
* @returns {ProjectAbsPath} absolute project path.
*
* @example
* myProject.retrieve('/home/user/myProject/abcd'); // => '/abcd'
*
* myProject.retrieve('/home/user/myProject/abcd/efgh'); // => '/abcd/efgh'
*
* myProject.retrieve('/home/user/myProject'); // => '/'
*
* myProject.retrieve('/home/user'); // => 'null'
*/
retrieve (_path) {
if (typeof _path !== 'string') throw new Error(`_path(${_path}) is not a string type.`);
if (!path.isAbsolute(_path)) throw new Error(`_path(${_path}) is not an absolute path.`);
if (path.equal(this.basedir, _path)) return "/";
if (!path.isParent(this.basedir, _path)) return null;
let relPath = _path.substr(this.basedir.length);
if (relPath[0] != '/') relPath = '/' + relPath;
return relPath;
}
/**
* @public
* @instance
* @function parse
* @version 0.1.4
* @since 0.0.1
* @created 2017-12-26
* @memberof module:index~ProjectDir
* @description Parsing a path.
*
* @param {ProjectPath} _path - to parse path.
* @throws {Error} _path is not a string type.
* @returns {Object} parsed path's object.
*
* @example
* myProject.parse('/abcd');
* // {
* // root: '/home/user/myProject',
* // names: '.git',
* // wd: '/home/user/myProject',
* // path: 'abcd',
* // abs: '/abcd',
* // realPath: '/home/user/myProject/abcd'
* // }
*
* myProject.wd = '/abcd';
* myProject.parse('efgh');
* // {
* // root: '/home/user/myProject',
* // names: '.git',
* // wd: '/home/user/myProject/abcd',
* // path: 'efgh',
* // abs: '/abcd/efgh',
* // realPath: '/home/user/myProject/abcd/efgh'
* // }
*/
parse (_path) {
if (typeof _path !== 'string') throw new Error(`_path(${_path}) is not a string type.`);
let rPath = this.resolve(_path);
let ret = {
root: this.basedir,
names: this.basename,
wd: this.wd,
path: rPath == null ? null : path.relative(this.wd, rPath),
abs: rPath == null ? null : "/"+path.relative(this.basedir, path.resolve(this.wd, rPath)),
realPath: path.resolve(this.wd, rPath == null ? _path.replace(/^\//, '') : rPath)
};
return ret;
}
/**
* @public
* @instance
* @function equal
* @version 0.1.4
* @since 0.1.2
* @created 2018-01-23
* @memberof module:index~ProjectDir
* @description Check same directory in project directory
*
* @param {ProjectPath|ProjectAbsPath} _path1 - To check project path
* @param {ProjectPath|ProjectAbsPath} _path2 - To check project path
* @throws {Error} Type error
* @returns {boolean} If same directory, returns true
*
* @example
* myProject.wd = '/abcd'
* console.log(myProject.wd); // => '/home/user/myProject/abcd'
*
* myProject.equal('/abcd/efgh', 'efgh'); // => true
* myProject.equal('/aaaa', '../aaaa'); // => true
* myProject.equal('/abcd', 'abcd'); // => false
*/
equal (_path1, _path2) {
if (typeof _path1 !== 'string') throw new Error(`_path1(${_path1}) is not a string type.`);
if (typeof _path2 !== 'string') throw new Error(`_path2(${_path2}) is not a string type.`);
let p1 = this.resolve(_path1),
p2 = this.resolve(_path2);
return path.equal(p1, p2);
}
/**
* @public
* @instance
* @function toRoot
* @version 0.1.4
* @since 0.1.2
* @created 2018-01-23
* @memberof module:index~ProjectDir
* @description Go to root directory
*
* @param {ProjectPath|ProjectAbsPath} _path - Start point of project path.
* @param {Function} func - Recursively executed function. the argument is current path.
* @returns {Path} Root directory of project.
*/
toRoot (_path, ...args) {
let callback = args.pop(),
options = args[0] || {};
options = Object.assign({
resolve: undefined,
reliable: undefined,
checker: (_p) => path.equal(_p, this.basedir)
}, options);
return path.toRoot(this.resolve(_path), options, callback);
}
/**
* @public
* @instance
* @function fromRoot
* @version 0.1.4
* @since 0.1.4
* @created 2018-02-01
* @memberof module:index~ProjectDir
* @description Go to _path from root path of current project.
*
* @param {ProjectPath|ProjectAbsPath} _path - End point of project path.
* @param {Function} func - Recursively executed function. the argument is current path.
* @returns {Path} _path directory of project if no breaks.
*/
fromRoot (_path, ...args) {
let callback = args.pop(),
options = args[0] || {};
options = Object.assign({
reliable: undefined,
start: this.basedir,
checker: (_p) => path.equal(_p, _path)
}, options);
return path.fromRoot(this.resolve(_path), options, callback);
}
}
module.exports = ProjectDir;