Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add depth specification to ls. Fixes #726 #1463

Merged
merged 1 commit into from
Nov 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions __tests__/commands/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* @flow */

import * as ls from '../../src/cli/commands/ls.js';

test('getParent should extract a parent object from a hash, if the parent key exists', () => {
const mockTreesByKey = {};

mockTreesByKey['parentPkg'] = {
name: '[email protected]',
children: [],
};
const res = ls.getParent('parentPkg#childPkg', mockTreesByKey);

expect(res instanceof Object).toBe(true);
expect(res.name).toBe('[email protected]');
expect(res.children.length).toBe(0);
});

test('getParent should return undefined if the key does not exist in hash', () => {
const mockTreesByKey = {};
mockTreesByKey['parentPkg'] = { };

const res = ls.getParent('parentPkg#childPkg', mockTreesByKey);
expect(res.name).not.toBeDefined();
expect(res.children).not.toBeDefined();
});

test('setFlags should set options for --depth', () => {
const flags = ['--depth'];
const commander = require('commander');
ls.setFlags(commander);

const commanderOptions = commander.options;
const optsLen = commanderOptions.length;
flags.map((flag) => {
let currFlagExists = false;
for (let i = 0; i < optsLen; i++) {
if (commanderOptions[i].long === flag) {
currFlagExists = true;
}
}
expect(currFlagExists).toBeTruthy();
});
});

test('setFlags should set options for --depth', () => {
const flags = ['--foo', '--bar', '--baz'];
const commander = require('commander');
ls.setFlags(commander);

const commanderOptions = commander.options;
const optsLen = commanderOptions.length;
flags.map((flag) => {
let currFlagExists = false;
for (let i = 0; i < optsLen; i++) {
if (commanderOptions[i].long === flag) {
currFlagExists = true;
}
}
expect(currFlagExists).not.toBeTruthy();
});
});

test('getReqDepth should return a number if valid', () => {
expect(ls.getReqDepth('1')).toEqual(1);
expect(ls.getReqDepth('01')).toEqual(1);
});

test('getReqDepth should return -1 if invalid', () => {
expect(ls.getReqDepth('foo')).toEqual(-1);
expect(ls.getReqDepth('bar')).toEqual(-1);
expect(ls.getReqDepth('')).toEqual(-1);
});
7 changes: 6 additions & 1 deletion src/cli/commands/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {Reporter} from '../../reporters/index.js';
import type {InstallCwdRequest, InstallPrepared} from './install.js';
import type {DependencyRequestPatterns} from '../../types.js';
import type Config from '../../config.js';
import type {LsOptions} from './ls.js';
import Lockfile from '../../lockfile/wrapper.js';
import * as PackageReference from '../../package-reference.js';
import PackageRequest from '../../package-request.js';
Expand Down Expand Up @@ -82,7 +83,11 @@ export class Add extends Install {
*/

async maybeOutputSaveTree(patterns: Array<string>): Promise<void> {
const {trees, count} = await buildTree(this.resolver, this.linker, patterns, true, true);
// don't limit the shown tree depth
const opts: LsOptions = {
reqDepth: 0,
};
const {trees, count} = await buildTree(this.resolver, this.linker, patterns, opts, true, true);
this.reporter.success(
count === 1 ?
this.reporter.lang('savedNewDependency')
Expand Down
72 changes: 54 additions & 18 deletions src/cli/commands/ls.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const invariant = require('invariant');

export const requireLockfile = true;

export type LsOptions = {
reqDepth?: ?number,
};

function buildCount(trees: ?Trees): number {
if (!trees || !trees.length) {
return 0;
Expand All @@ -36,6 +40,7 @@ export async function buildTree(
resolver: PackageResolver,
linker: PackageLinker,
patterns: Array<string>,
opts: LsOptions,
onlyFresh?: boolean,
ignoreHoisted?: boolean,
): Promise<{
Expand All @@ -54,6 +59,11 @@ export async function buildTree(
// build initial trees
for (const [, info] of hoisted) {
const ref = info.pkg._reference;
const hint = null;
const parent = getParent(info.key, treesByKey);
const children = [];
let depth = 0;
let color = 'bold';
invariant(ref, 'expected reference');

if (onlyFresh) {
Expand All @@ -69,29 +79,39 @@ export async function buildTree(
}
}

const hint = null;
let color = 'bold';

if (info.originalKey !== info.key) {
if (info.originalKey !== info.key || opts.reqDepth === 0) {
// was hoisted
color = null;
}
// check parent to obtain next depth
if (parent && parent.depth > 0) {
depth = parent.depth + 1;
} else {
depth = 0;
}

const children = [];
treesByKey[info.key] = {
name: `${info.pkg.name}@${info.pkg.version}`,
children,
hint,
color,
};
const topLevel = opts.reqDepth === 0 && !parent;
const showAll = opts.reqDepth === -1;
const nextDepthIsValid = (depth + 1 <= Number(opts.reqDepth));

if (topLevel || nextDepthIsValid || showAll) {
treesByKey[info.key] = {
name: `${info.pkg.name}@${info.pkg.version}`,
children,
hint,
color,
depth,
};
}

// add in dummy children for hoisted dependencies
const nextChildDepthIsValid = (depth + 1 < Number(opts.reqDepth));
invariant(ref, 'expected reference');
if (!ignoreHoisted) {
if ((!ignoreHoisted && nextDepthIsValid) || showAll) {
for (const pattern of resolver.dedupePatterns(ref.dependencies)) {
const pkg = resolver.getStrictResolvedPattern(pattern);

if (!hoistedByKey[`${info.key}#${pkg.name}`]) {
if (!hoistedByKey[`${info.key}#${pkg.name}`] && (nextChildDepthIsValid || showAll)) {
children.push({
name: pattern,
color: 'dim',
Expand All @@ -105,18 +125,16 @@ export async function buildTree(
// add children
for (const [, info] of hoisted) {
const tree = treesByKey[info.key];
const parent = getParent(info.key, treesByKey);
if (!tree) {
continue;
}

const keyParts = info.key.split('#');
if (keyParts.length === 1) {
if (info.key.split('#').length === 1) {
trees.push(tree);
continue;
}

const parentKey = keyParts.slice(0, -1).join('#');
const parent = treesByKey[parentKey];
if (parent) {
parent.children.push(tree);
}
Expand All @@ -125,17 +143,35 @@ export async function buildTree(
return {trees, count: buildCount(trees)};
}

export function getParent(key: string, treesByKey: Object) : Object {
const parentKey = key.split('#').slice(0, -1).join('#');
return treesByKey[parentKey];
}

export function setFlags(commander: Object) {
commander.option('--depth [depth]', 'Limit the depth of the shown dependencies');
}

export function getReqDepth(inputDepth: string) : number {
return inputDepth && /^\d+$/.test(inputDepth) ? Number(inputDepth) : -1;
}

export async function run(
config: Config,
reporter: Reporter,
flags: Object,
args: Array<string>,
): Promise<void> {

const lockfile = await Lockfile.fromDirectory(config.cwd, reporter);
const install = new Install(flags, config, reporter, lockfile);
const [depRequests, patterns] = await install.fetchRequestFromCwd();
await install.resolver.init(depRequests, install.flags.flat);

const opts: LsOptions = {
reqDepth: getReqDepth(flags.depth),
};

let filteredPatterns: Array<string> = [];

if (args.length) {
Expand All @@ -161,6 +197,6 @@ export async function run(
filteredPatterns = patterns;
}

const {trees} = await buildTree(install.resolver, install.linker, filteredPatterns);
const {trees} = await buildTree(install.resolver, install.linker, filteredPatterns, opts);
reporter.tree('ls', trees);
}
2 changes: 1 addition & 1 deletion src/reporters/base-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export default class BaseReporter {
// TODO
list(key: string, items: Array<string>) {}

// TODO
// Outputs basic tree structure to console
tree(key: string, obj: Trees) {}

// called whenever we begin a step in the CLI.
Expand Down
44 changes: 8 additions & 36 deletions src/reporters/console/console-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,14 @@ import Progress from './progress-bar.js';
import Spinner from './spinner-progress.js';
import {clearLine} from './util.js';
import {removeSuffix} from '../../util/misc.js';
import {sortTrees, recurseTree, getFormattedOutput} from './helpers/tree-helper.js';

const {inspect} = require('util');
const readline = require('readline');
const repeat = require('repeating');
const chalk = require('chalk');
const read = require('read');

function sortTrees(trees: Trees = []): Trees {
return trees.sort(function(tree1, tree2): number {
return tree1.name.localeCompare(tree2.name);
});
}

type Row = Array<string>;

export default class ConsoleReporter extends BaseReporter {
Expand Down Expand Up @@ -186,42 +181,19 @@ export default class ConsoleReporter extends BaseReporter {
});
});
}

// handles basic tree output to console
tree(key: string, trees: Trees) {
trees = sortTrees(trees);

const stdout = this.stdout;

//
const output = ({name, children, hint, color}, level, end) => {
children = sortTrees(children);

let indent = end ? '└' : '├';

if (level) {
indent = repeat('│ ', level) + indent;
}

let suffix = '';
if (hint) {
suffix += ` (${this.format.dim(hint)})`;
}
if (color) {
name = this.format[color](name);
}
stdout.write(`${indent}─ ${name}${suffix}\n`);
const formatter = this.format;
const out = getFormattedOutput({end, level, hint, color, name, formatter});
this.stdout.write(out);

if (children && children.length) {
for (let i = 0; i < children.length; i++) {
const tree = children[i];
output(tree, level + 1, i === children.length - 1);
}
recurseTree(sortTrees(children), level, output);
}
};

for (let i = 0; i < trees.length; i++) {
const tree = trees[i];
output(tree, 0, i === trees.length - 1);
}
recurseTree(sortTrees(trees), -1, output);
}

activitySet(total: number, workers: number): ReporterSpinnerSet {
Expand Down
57 changes: 57 additions & 0 deletions src/reporters/console/helpers/tree-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* @flow */

const repeat = require('repeating');

// types
import type {Trees} from '../../types.js';

export type FormattedOutput = {
end: boolean,
level: number,
hint: any,
color: string,
name: string,
formatter: any,
};

// public
export const sortTrees = (trees: Trees): Trees => {
return trees.sort(function(tree1, tree2): number {
return tree1.name.localeCompare(tree2.name);
});
};

export const recurseTree = (tree: Trees, level: number, recurseFunc: Function) => {
const treeLen = tree.length;
const treeEnd = treeLen - 1;
for (let i = 0; i < treeLen; i++) {
recurseFunc(tree[i], level + 1, i === treeEnd);
}
};

export const getFormattedOutput = (fmt: FormattedOutput): string => {
const item = formatColor(fmt.color, fmt.name, fmt.formatter);
const indent = getIndent(fmt.end, fmt.level);
const suffix = getSuffix(fmt.hint, fmt.formatter);
return `${indent}─ ${item}${suffix}\n`;
};

// private
const getIndentChar = (end: boolean) : string => {
return end ? '└' : '├';
};

const getIndent = (end: boolean, level: number) : string => {
const base = repeat('│ ', level);
const indentChar = getIndentChar(end);
const hasLevel = base + indentChar;
return level ? hasLevel : indentChar;
};

const getSuffix = (hint: any, formatter: any) : string => {
return hint ? ` (${formatter.grey(hint)})` : '';
};

const formatColor = (color: string, strToFormat: string, formatter: any) : string => {
return color ? formatter[color](strToFormat) : strToFormat;
};