提交 9de08e28 编写于 作者: J João Moreno

Merge pull request #5754 from joaomoreno/cli

Use minimist for argv handling
{
"name": "Code",
"version": "1.0.0",
"version": "1.0.1",
"dependencies": {
"agent-base": {
"version": "1.0.2",
......@@ -278,6 +278,11 @@
"from": "minimatch@>=0.2.12 <0.3.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz"
},
"minimist": {
"version": "1.2.0",
"from": "minimist@latest",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
},
"ms": {
"version": "0.7.1",
"from": "ms@0.7.1",
......
#!/usr/bin/env bash
if [[ "$OSTYPE" == "darwin"* ]]; then
realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; }
ROOT=$(dirname $(dirname $(realpath "$0")))
else
ROOT=$(dirname $(dirname $(readlink -f $0)))
fi
function code() {
cd $ROOT
# Node modules
test -d node_modules || ./scripts/npm.sh install
# Get electron
./node_modules/.bin/gulp electron
# Build
test -d out || ./node_modules/.bin/gulp compile
# Launch Code
[[ "$OSTYPE" == "darwin"* ]] \
&& ELECTRON=.build/electron/Electron.app/Contents/MacOS/Electron \
|| ELECTRON=.build/electron/electron
CLI="$ROOT/out/cli.js"
ATOM_SHELL_INTERNAL_RUN_AS_NODE=1 \
NODE_ENV=development \
VSCODE_DEV=1 \
ELECTRON_ENABLE_LOGGING=1 \
ELECTRON_ENABLE_STACK_DUMPING=1 \
"$ELECTRON" "$CLI" . $@
}
code "$@"
......@@ -6,5 +6,4 @@
"repositoryURL": "https://github.com/DefinitelyTyped/DefinitelyTyped",
"license": "MIT",
"isDev": true
}
]
\ No newline at end of file
}]
\ No newline at end of file
// Type definitions for minimist 1.1.3
// Project: https://github.com/substack/minimist
// Definitions by: Bart van der Schoor <https://github.com/Bartvds>, Necroskillz <https://github.com/Necroskillz>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare module 'minimist' {
function minimist(args?: string[], opts?: minimist.Opts): minimist.ParsedArgs;
namespace minimist {
export interface Opts {
// a string or array of strings argument names to always treat as strings
string?: string|string[];
// a string or array of strings to always treat as booleans
boolean?: boolean|string|string[];
// an object mapping string names to strings or arrays of string argument names to use
alias?: {[key:string]: string|string[]};
// an object mapping string argument names to default values
default?: {[key:string]: any};
// when true, populate argv._ with everything after the first non-option
stopEarly?: boolean;
// a function which is invoked with a command line parameter not defined in the opts configuration object.
// If the function returns false, the unknown option is not added to argv
unknown?: (arg: string) => boolean;
// when true, populate argv._ with everything before the -- and argv['--'] with everything after the --
'--'?: boolean;
}
export interface ParsedArgs {
[arg: string]: any;
_: string[];
}
}
export = minimist;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import * as minimist from 'minimist';
import pkg from './package';
export interface ParsedArgs extends minimist.ParsedArgs {
help: boolean;
version: boolean;
wait: boolean;
diff: boolean;
goto: boolean;
'new-window': boolean;
'reuse-window': boolean;
locale: string;
'user-data-dir': string;
performance: boolean;
verbose: boolean;
logExtensionHostCommunication: boolean;
'disable-extensions': boolean;
extensionHomePath: string;
extensionDevelopmentPath: string;
extensionTestsPath: string;
timestamp: string;
debugBrkPluginHost: string;
debugPluginHost: string;
}
const options: minimist.Opts = {
string: [
'locale',
'user-data-dir',
'extensionHomePath',
'extensionDevelopmentPath',
'extensionTestsPath',
'timestamp'
],
boolean: [
'help',
'version',
'wait',
'diff',
'goto',
'new-window',
'reuse-window',
'performance',
'verbose',
'logExtensionHostCommunication',
'disable-extensions'
],
alias: {
help: 'h',
version: 'v',
wait: 'w',
diff: 'd',
goto: 'g',
'new-window': 'n',
'reuse-window': 'r',
performance: 'p',
'disable-extensions': 'disableExtensions'
}
};
export function parseArgs(args: string[]) {
return minimist(args, options) as ParsedArgs;
}
const executable = 'code' + (os.platform() === 'win32' ? '.exe' : '');
const indent = ' ';
export const helpMessage = `Visual Studio Code v${ pkg.version }
Usage: ${ executable } [arguments] [paths...]
Options:
${ indent }-d, --diff Open a diff editor. Requires to pass two file paths
${ indent } as arguments.
${ indent }--disable-extensions Disable all installed extensions.
${ indent }-g, --goto Open the file at path at the line and column (add
${ indent } :line[:column] to path).
${ indent }-h, --help Print usage.
${ indent }--locale <locale> The locale to use (e.g. en-US or zh-TW).
${ indent }-n, --new-window Force a new instance of Code.
${ indent }-r, --reuse-window Force opening a file or folder in the last active
${ indent } window.
${ indent }--user-data-dir <dir> Specifies the directory that user data is kept in,
${ indent } useful when running as root.
${ indent }-v, --version Print version.
${ indent }-w, --wait Wait for the window to be closed before returning.`;
......@@ -3,73 +3,35 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
import { spawn } from 'child_process';
import uri from 'vs/base/common/uri';
import { assign } from 'vs/base/common/objects';
import { parseArgs, helpMessage } from './argv';
import pkg from './package';
const rootPath = path.dirname(uri.parse(require.toUrl('')).fsPath);
const packageJsonPath = path.join(rootPath, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
export function main(args: string[]) {
const argv = parseArgs(args);
class ArgParser {
constructor(private argv: string[]) {}
hasFlag(flag, alias): boolean {
return (flag && this.argv.indexOf('--' + flag) >= 0)
|| (alias && this.argv.indexOf('-' + alias) >= 0);
}
help(): string {
const executable = 'code' + (os.platform() === 'win32' ? '.exe' : '');
const indent = ' ';
return `Visual Studio Code v${ packageJson.version }
Usage: ${ executable } [arguments] [paths...]
Options:
${ indent }-d, --diff Open a diff editor. Requires to pass two file paths
${ indent } as arguments.
${ indent }--disable-extensions Disable all installed extensions.
${ indent }-g, --goto Open the file at path at the line and column (add
${ indent } :line[:column] to path).
${ indent }-h, --help Print usage.
${ indent }--locale=LOCALE The locale to use (e.g. en-US or zh-TW).
${ indent }-n, --new-window Force a new instance of Code.
${ indent }-r, --reuse-window Force opening a file or folder in the last active
${ indent } window.
${ indent }--user-data-dir=DIR Specifies the directory that user data is kept in,
${ indent } useful when running as root.
${ indent }-v, --version Print version.
${ indent }-w, --wait Wait for the window to be closed before returning.`;
}
}
export function main(argv: string[]) {
const argParser = new ArgParser(argv);
let exit = true;
if (argParser.hasFlag('help', 'h')) {
console.log(argParser.help());
} else if (argParser.hasFlag('version', 'v')) {
console.log(packageJson.version);
if (argv.help) {
console.log(helpMessage);
} else if (argv.version) {
console.log(pkg.version);
} else {
delete process.env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'];
if (argParser.hasFlag('wait', 'w')) {
exit = false;
const env = assign({}, process.env);
delete env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'];
const child = spawn(process.execPath, args, {
detached: true,
stdio: 'ignore',
env
});
let child = spawn(process.execPath, process.argv.slice(2), { detached: true, stdio: 'ignore' });
if (argv.wait) {
child.on('exit', process.exit);
} else {
spawn(process.execPath, process.argv.slice(2), { detached: true, stdio: 'ignore' });
return;
}
}
if (exit) {
process.exit(0);
}
process.exit(0);
}
main(process.argv.slice(2));
......@@ -15,47 +15,10 @@ import strings = require('vs/base/common/strings');
import paths = require('vs/base/common/paths');
import platform = require('vs/base/common/platform');
import uri from 'vs/base/common/uri';
import { assign } from 'vs/base/common/objects';
import types = require('vs/base/common/types');
import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation';
export interface IProductConfiguration {
nameShort: string;
nameLong: string;
applicationName: string;
win32AppUserModelId: string;
win32MutexName: string;
darwinBundleIdentifier: string;
dataFolderName: string;
downloadUrl: string;
updateUrl?: string;
quality?: string;
commit: string;
date: string;
extensionsGallery: {
serviceUrl: string;
itemUrl: string;
};
extensionTips: { [id: string]: string; };
crashReporter: Electron.CrashReporterStartOptions;
welcomePage: string;
enableTelemetry: boolean;
aiConfig: {
key: string;
asimovKey: string;
};
sendASmile: {
reportIssueUrl: string,
requestFeatureUrl: string
};
documentationUrl: string;
releaseNotesUrl: string;
twitterUrl: string;
requestFeatureUrl: string;
reportIssueUrl: string;
licenseUrl: string;
privacyStatementUrl: string;
}
import product, {IProductConfiguration} from './product';
import { parseArgs } from './argv';
export interface IProcessEnvironment {
[key: string]: string;
......@@ -104,6 +67,20 @@ export interface IEnvironmentService {
sharedIPCHandle: string;
}
function getNumericValue(value: string, defaultValue: number, fallback: number = void 0) {
const numericValue = parseInt(value);
if (types.isNumber(numericValue)) {
return numericValue;
}
if (value) {
return defaultValue;
}
return fallback;
}
export class EnvService implements IEnvironmentService {
serviceId = IEnvironmentService;
......@@ -119,11 +96,9 @@ export class EnvService implements IEnvironmentService {
get isBuilt(): boolean { return !process.env['VSCODE_DEV']; }
private _product: IProductConfiguration;
get product(): IProductConfiguration { return this._product; }
get updateUrl(): string { return this.product.updateUrl; }
get quality(): string { return this.product.quality; }
get product(): IProductConfiguration { return product; }
get updateUrl(): string { return product.updateUrl; }
get quality(): string { return product.quality; }
private _userHome: string;
get userHome(): string { return this._userHome; }
......@@ -184,58 +159,36 @@ export class EnvService implements IEnvironmentService {
args = [...extraargs, ...args];
}
const debugBrkExtensionHostPort = parseNumber(args, '--debugBrkPluginHost', 5870);
let debugExtensionHostPort: number;
let debugBrkExtensionHost: boolean;
const argv = parseArgs(args);
if (debugBrkExtensionHostPort) {
debugExtensionHostPort = debugBrkExtensionHostPort;
debugBrkExtensionHost = true;
} else {
debugExtensionHostPort = parseNumber(args, '--debugPluginHost', 5870, this.isBuilt ? void 0 : 5870);
}
const opts = parseOpts(args);
const gotoLineMode = !!opts['g'] || !!opts['goto'];
const pathArguments = parsePathArguments(this._currentWorkingDirectory, args, gotoLineMode);
const debugBrkExtensionHostPort = getNumericValue(argv.debugBrkPluginHost, 5870);
const debugExtensionHostPort = getNumericValue(argv.debugPluginHost, 5870, this.isBuilt ? void 0 : 5870);
const pathArguments = parsePathArguments(this._currentWorkingDirectory, argv._, argv.goto);
const timestamp = parseInt(argv.timestamp);
this._cliArgs = Object.freeze({
pathArguments: pathArguments,
programStart: parseNumber(args, '--timestamp', 0, 0),
enablePerformance: !!opts['p'],
verboseLogging: !!opts['verbose'],
debugExtensionHostPort: debugExtensionHostPort,
debugBrkExtensionHost: debugBrkExtensionHost,
logExtensionHostCommunication: !!opts['logExtensionHostCommunication'],
openNewWindow: !!opts['n'] || !!opts['new-window'],
openInSameWindow: !!opts['r'] || !!opts['reuse-window'],
gotoLineMode: gotoLineMode,
diffMode: (!!opts['d'] || !!opts['diff']) && pathArguments.length === 2,
extensionsHomePath: normalizePath(parseString(args, '--extensionHomePath')),
extensionDevelopmentPath: normalizePath(parseString(args, '--extensionDevelopmentPath')),
extensionTestsPath: normalizePath(parseString(args, '--extensionTestsPath')),
disableExtensions: !!opts['disableExtensions'] || !!opts['disable-extensions'],
locale: parseString(args, '--locale'),
waitForWindowClose: !!opts['w'] || !!opts['wait']
programStart: types.isNumber(timestamp) ? timestamp : 0,
enablePerformance: argv.performance,
verboseLogging: argv.verbose,
debugExtensionHostPort: debugBrkExtensionHostPort || debugExtensionHostPort,
debugBrkExtensionHost: !!debugBrkExtensionHostPort,
logExtensionHostCommunication: argv.logExtensionHostCommunication,
openNewWindow: argv['new-window'],
openInSameWindow: argv['reuse-window'],
gotoLineMode: argv.goto,
diffMode: argv.diff && pathArguments.length === 2,
extensionsHomePath: normalizePath(argv.extensionHomePath),
extensionDevelopmentPath: normalizePath(argv.extensionDevelopmentPath),
extensionTestsPath: normalizePath(argv.extensionTestsPath),
disableExtensions: argv['disable-extensions'],
locale: argv.locale,
waitForWindowClose: argv.wait
});
this._isTestingFromCli = this.cliArgs.extensionTestsPath && !this.cliArgs.debugBrkExtensionHost;
try {
this._product = JSON.parse(fs.readFileSync(path.join(this._appRoot, 'product.json'), 'utf8'));
} catch (error) {
this._product = Object.create(null);
}
if (!this.isBuilt) {
const nameShort = `${ this._product.nameShort } Dev`;
const nameLong = `${ this._product.nameLong } Dev`;
const dataFolderName = `${ this._product.dataFolderName }-dev`;
this._product = assign(this._product, { nameShort, nameLong, dataFolderName });
}
this._userHome = path.join(app.getPath('home'), this._product.dataFolderName);
this._userHome = path.join(app.getPath('home'), product.dataFolderName);
// TODO move out of here!
if (!fs.existsSync(this._userHome)) {
......@@ -299,57 +252,45 @@ export class EnvService implements IEnvironmentService {
}
}
type OptionBag = { [opt: string]: boolean; };
function parsePathArguments(cwd: string, args: string[], gotoLineMode?: boolean): string[] {
const result = args.map(arg => {
let pathCandidate = arg;
function parseOpts(argv: string[]): OptionBag {
return argv
.filter(a => /^-/.test(a))
.map(a => a.replace(/^-*/, ''))
.reduce((r, a) => { r[a] = true; return r; }, <OptionBag>{});
}
let parsedPath: IParsedPath;
if (gotoLineMode) {
parsedPath = parseLineAndColumnAware(arg);
pathCandidate = parsedPath.path;
}
function parsePathArguments(cwd: string, argv: string[], gotoLineMode?: boolean): string[] {
return arrays.coalesce( // no invalid paths
arrays.distinct( // no duplicates
argv.filter(a => !(/^-/.test(a))) // arguments without leading "-"
.map((arg) => {
let pathCandidate = arg;
let parsedPath: IParsedPath;
if (gotoLineMode) {
parsedPath = parseLineAndColumnAware(arg);
pathCandidate = parsedPath.path;
}
if (pathCandidate) {
pathCandidate = preparePath(cwd, pathCandidate);
}
let realPath: string;
try {
realPath = fs.realpathSync(pathCandidate);
} catch (error) {
// in case of an error, assume the user wants to create this file
// if the path is relative, we join it to the cwd
realPath = path.normalize(path.isAbsolute(pathCandidate) ? pathCandidate : path.join(cwd, pathCandidate));
}
if (!paths.isValidBasename(path.basename(realPath))) {
return null; // do not allow invalid file names
}
if (gotoLineMode) {
parsedPath.path = realPath;
return toLineAndColumnPath(parsedPath);
}
return realPath;
}),
(element) => {
return element && (platform.isWindows || platform.isMacintosh) ? element.toLowerCase() : element; // only linux is case sensitive on the fs
}
)
);
if (pathCandidate) {
pathCandidate = preparePath(cwd, pathCandidate);
}
let realPath: string;
try {
realPath = fs.realpathSync(pathCandidate);
} catch (error) {
// in case of an error, assume the user wants to create this file
// if the path is relative, we join it to the cwd
realPath = path.normalize(path.isAbsolute(pathCandidate) ? pathCandidate : path.join(cwd, pathCandidate));
}
if (!paths.isValidBasename(path.basename(realPath))) {
return null; // do not allow invalid file names
}
if (gotoLineMode) {
parsedPath.path = realPath;
return toLineAndColumnPath(parsedPath);
}
return realPath;
});
const caseInsensitive = platform.isWindows || platform.isMacintosh;
const distinct = arrays.distinct(result, e => e && caseInsensitive ? e.toLowerCase() : e);
return arrays.coalesce(distinct);
}
function preparePath(cwd: string, p: string): string {
......@@ -378,34 +319,6 @@ function normalizePath(p?: string): string {
return p ? path.normalize(p) : p;
}
function parseNumber(argv: string[], key: string, defaultValue?: number, fallbackValue?: number): number {
let value: number;
for (let i = 0; i < argv.length; i++) {
let segments = argv[i].split('=');
if (segments[0] === key) {
value = Number(segments[1]) || defaultValue;
break;
}
}
return types.isNumber(value) ? value : fallbackValue;
}
function parseString(argv: string[], key: string, defaultValue?: string, fallbackValue?: string): string {
let value: string;
for (let i = 0; i < argv.length; i++) {
let segments = argv[i].split('=');
if (segments[0] === key) {
value = String(segments[1]) || defaultValue;
break;
}
}
return types.isString(value) ? strings.trim(value, '"') : fallbackValue;
}
export function getPlatformIdentifier(): string {
if (process.platform === 'linux') {
return `linux-${process.arch}`;
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import uri from 'vs/base/common/uri';
export interface IPackageConfiguration {
version: string;
}
const rootPath = path.dirname(uri.parse(require.toUrl('')).fsPath);
const packageJsonPath = path.join(rootPath, 'package.json');
export default require.__$__nodeRequire(packageJsonPath) as IPackageConfiguration;
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import uri from 'vs/base/common/uri';
export interface IProductConfiguration {
nameShort: string;
nameLong: string;
applicationName: string;
win32AppUserModelId: string;
win32MutexName: string;
darwinBundleIdentifier: string;
dataFolderName: string;
downloadUrl: string;
updateUrl?: string;
quality?: string;
commit: string;
date: string;
extensionsGallery: {
serviceUrl: string;
itemUrl: string;
};
extensionTips: { [id: string]: string; };
crashReporter: Electron.CrashReporterStartOptions;
welcomePage: string;
enableTelemetry: boolean;
aiConfig: {
key: string;
asimovKey: string;
};
sendASmile: {
reportIssueUrl: string,
requestFeatureUrl: string
};
documentationUrl: string;
releaseNotesUrl: string;
twitterUrl: string;
requestFeatureUrl: string;
reportIssueUrl: string;
licenseUrl: string;
privacyStatementUrl: string;
}
const rootPath = path.dirname(uri.parse(require.toUrl('')).fsPath);
const productJsonPath = path.join(rootPath, 'product.json');
const product = require.__$__nodeRequire(productJsonPath) as IProductConfiguration;
if (process.env['VSCODE_DEV']) {
product.nameShort += ' Dev';
product.nameLong += ' Dev';
product.dataFolderName += '-dev';
}
export default product;
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册