如何用node编写命令行工具,附上一个ginit示例,并推荐好用的命令行工具
强大的 Node.js 除了能写传统的 Web 应用,其实还有更广泛的用途。微服务、REST API、各种工具……甚至还能开发物联网和桌面应用。JavaScript 不愧是宇宙第一语言。
Node.js 在开发命令行工具方面也是相当方便,通过这篇教程我们可以来感受下。我们先看看跟命令行有关的几个第三方包,然后从零开始写一个真实的命令行工具。
这个 CLI 的用途就是初始化一个 Git 仓库。当然,底层就是调用了 git init
,但是它的功能不止这么简单。它还能从命令行创建一个远程的 Github 仓库,允许用户交互式地创建 .gitignore
文件,最后还能完成提交和推代码。
![](http://upload-images.jianshu.io/upload_images/1618526-4f40d20b011b2e65.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/751/format/webp)
为什么要用 Node.js 写命令行工具
在动手之前,我们有必要知道为什么选择 Node.js 开发命令行工具。
最明显优势就是——相信你已经猜到了——它是用 JavaScript 写的。
另外一个原因是 Node.js 生态系统非常完善,各种用途的 package 应有尽有,其中就有不少是专门为了开发命令行工具的。
最后一个原因是,用npm
管理依赖不用担心跨平台问题,不像 Aptitude、Yum 或者 Homebrew 这些针对特定操作系统的包管理工具,令人头疼。
注:这么说不一定准确,可能命令行只是需要其他的外部依赖。
动手写一个命令行工具: ginit
![](http://upload-images.jianshu.io/upload_images/1618526-e327844d86964d07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/652/format/webp)
在这篇教程里我们来开发一个叫做 ginit 的命令行工具。我们可以把它当做高配版的git init
。什么意思呢?我们都知道,git init
命令会在当前目录初始化一个 git 仓库。但是,这仅仅是创建或关联已有项目到 Git 仓库的其中一步而已。典型的工作流程是这样的:
- 运行
git init
初始化本地仓库 - 创建远程仓库(比如在 Github 或 Bitbucket 上)——这一步通常要脱离命令行,打开浏览器来操作
- 添加 remote
- 创建
.gitignore
文件 - 添加项目文件
- commit 本地文件
- push 到远程
可能还有更多步骤,为了演示我们只看关键部分。你会发现,这些步骤很多都是机械式、重复性的,为什么不用命令行来完成这些工作呢?比如复制粘贴 git 地址这种事情,能忍受手动操作?
ginit 可以做到:在当前目录创建 git 仓库,同时创建远程仓库(我们这里用 Github 演示),然后提供一个类似操作向导的界面来创建 .gitignore
文件,最后提交文件夹内容并推送到远程仓库。可能这也节省不了太多时间,但是它确实给创建新项目带来了些许便利。
好了,我们开始吧。
项目依赖
有一点是肯定的:说到外观,控制台无论如何也不会有图形界面那么复杂。尽管如此,也并不是说控制台一定是那种原始的纯文本丑陋界面。你会惊讶地发现,原来命令行也可以那么好看!我们会用到一些美化命令行界面的库: chalk 给输出内容着色, clui 提供一些可视化组件。还有更好玩的, figlet 可以生成炫酷的 ASCII 字符图案, clear 用来清除控制台。
输入输出方面,低端的 Readline Node.js 模块可以询问用户并接受输入,简单场景下够用了。但我们会用到一个更高端的工具—— Inquirer。除了询问用户的功能,它还提供简单的输入控件:单选框和复选框,这可是在命令行控制台啊,有点意外吧。
我们还用到 minimist 来解析命令行参数。
以下是完整列表:
- chalk :彩色输出
- clear : 清空命令行屏幕
- clui :绘制命令行中的表格、仪表盘、加载指示器等。
- figlet :生成字符图案
- inquirer :创建交互式的命令行界面
- minimist :解析参数
- configstore:轻松加载和保存配置
还有这些:
- @octokit/rest:Node.js 里的 GitHub REST API 客户端
- lodash:JavaScript 工具库
- simple-git:在 Node.js 应用程序中运行 Git 命令的工具
- touch:实现 Unix touch 命令的工具
开始
创建一个项目文件夹。
mkdir ginit
cd ginit
新建一个package.json
文件:
npm init
根据提示一路往下走:
name: (ginit)
version: (1.0.0)
description: "git init" on steroids
entry point: (index.js)
test command:
git repository:
keywords: Git CLI
author: [YOUR NAME]
license: (ISC)
安装依赖:
npm install chalk clear clui figlet inquirer minimist configstore @octokit/rest lodash simple-git touch --save
最终生成的 package.json
文件大概是这样的:
{
"name": "ginit",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Git",
"CLI"
],
"author": "",
"license": "ISC",
"bin": {
"ginit": "./index.js"
},
"dependencies": {
"@octokit/rest": "^14.0.5",
"chalk": "^2.3.0",
"clear": "0.0.1",
"clui": "^0.3.6",
"configstore": "^3.1.1",
"figlet": "^1.2.0",
"inquirer": "^5.0.1",
"lodash": "^4.17.4",
"minimist": "^1.2.0",
"simple-git": "^1.89.0",
"touch": "^3.1.0"
}
}
然后在这个文件夹里新建一个index.js
文件,加上这段代码:
const chalk = require('chalk');
const clear = require('clear');
const figlet = require('figlet');
添加一些 Helper 方法
接下来新建一个lib
文件夹,用来存放各种 helper 模块:
- files.js — 基本的文件管理
- inquirer.js — 命令行用户界面
- github.js — access token 管理
- repo.js — Git 仓库管理
先看 lib/files.js
,这里需要完成:
- 获取当前路径(文件夹名作为默认仓库名)
- 检查路径是否存在(通过查找名为
.git
的目录,判断当前目录是否已经是 Git 仓库)
看上去很简单直接,但这里还是有点坑的。
首先,你可能想用fs
模块的 realpathSync 方法获取当前路径:
path.basename(path.dirname(fs.realpathSync(__filename)));
当我们在同一路径下运行应用时(即 node index.js
),这没问题。但是要知道,我们要把这个控制台应用做成全局的,就是说我们想要的是当前工作目录的名称,而不是应用的安装路径。因此,最好使用 process.cwd:
path.basename(process.cwd());
其次,检查文件或目录是否存在的推荐方法一直在变。目前的方法是用fs.stat
或fs.statSync
。这两个方法在文件不存在的情况下会抛异常,所以我们要用try...catch
。
最后需要注意的是,当你在写命令行应用的时候,使用这些方法的同步版本就可以了。
整理下lib/files.js
代码,一个工具包就出来了:
const fs = require('fs');
const path = require('path');
module.exports = {
getCurrentDirectoryBase : () => {
return path.basename(process.cwd());
},
directoryExists : (filePath) => {
try {
return fs.statSync(filePath).isDirectory();
} catch (err) {
return false;
}
}
};
回到index.js
文件,引入这个文件:
const files = require('./lib/files');
有了这个,我们就可以动手开发应用了。
初始化 Node CLI
现在让我们来实现控制台应用的启动部分。
为了展示安装的这些控制台输出强化模块,我们先清空屏幕,然后展示一个banner:
clear();
console.log(
chalk.yellow(
figlet.textSync('Ginit', { horizontalLayout: 'full' })
)
);
输出效果如下图:
![](http://upload-images.jianshu.io/upload_images/1618526-c6b6784df6a2f39c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/652/format/webp)
接着运行简单的检查,确保当前目录不是 Git 仓库。很容易,我们只要用刚才创建的工具方法检查是否存在 .git
文件夹就行了:
if (files.directoryExists('.git')) {
console.log(chalk.red('Already a git repository!'));
process.exit();
}
提示:我们用了 chalk 模块 来展示红色的消息。
提示用户输入
接下来我们要做的就是写个函数,提示用户输入 Github 登录凭证。这个可以用 Inquirer 来实现。这个模块包含一些支持各种提示类型的方法,语法上跟 HTML 表单控件类似。为了收集用户的 Github 用户名和密码,我们分别用了 input
和 password
类型。
首先新建 lib/inquirer.js
文件,加入以下代码:
const inquirer = require('inquirer');
const files = require('./files');
module.exports = {
askGithubCredentials: () => {
const questions = [
{
name: 'username',
type: 'input',
message: 'Enter your GitHub username or e-mail address:',
validate: function( value ) {
if (value.length) {
return true;
} else {
return 'Please enter your username or e-mail address.';
}
}
},
{
name: 'password',
type: 'password',
message: 'Enter your password:',
validate: function(value) {
if (value.length) {
return true;
} else {
return 'Please enter your password.';
}
}
}
];
return inquirer.prompt(questions);
},
}
如你所见,inquirer.prompt()
向用户询问一系列问题,并以数组的形式作为参数传入。数组的每个元素都是一个对象,分别定义了name
、 type
和message
属性。
用户提供的输入信息返回一个 promise 给调用函数。如果成功,我们会得到一个对象,包含username
和password
属性。
可以在index.js
里测试下:
const inquirer = require('./lib/inquirer');
const run = async () => {
const credentials = await inquirer.askGithubCredentials();
console.log(credentials);
}
run();
运行 node index.js
:
![](http://upload-images.jianshu.io/upload_images/1618526-551395fecacf7b41.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/652/format/webp)
处理 GitHub 身份验证
下一步是创建一个函数,用来获取 Github API 的OAuth token。我们实际上是用用户名和密码换取 token的。
当然了,我们不能让用户每次使用这个工具的时候都需要输入身份凭证,而是把 OAuth token 存起来给后续请求使用。这个是就要用到 configstore 这个包了。
保存配置信息
保存配置信息表面上看起来非常简单直接:无需第三方库,直接存取 JSON 文件就好了。但是,configstore 这个包还有几个关键的优势:
- 它会根据你的操作系统和当前用户来决定最佳的文件存储位置。
- 不需要直接读写文件,只要修改 configstore 对象,后面的事都帮你搞定了。
用法也很简单,创建一个实例,传入应用标识符就行了。例如:
const Configstore = require('configstore');
const conf = new Configstore('ginit');
如果 configstore
文件不存在,它会返回一个空对象并在后台创建该文件。如果 configstore
文件已经存在,内容会被解析成 JSON,应用程序就可以使用它了。 你可以把 conf
当成简单的对象,根据需要获取或设置属性。刚才已经说了,你不用担心保存的问题,它已经帮你做好了。
提示:macOS/Linux 系统该文件位于 /Users/[YOUR-USERNME]/.config/configstore/ginit.json
与 GitHub API 通信
让我们写一个库,处理 GitHub token。新建文件 lib/github.js
并添加以下代码:
const octokit = require('@octokit/rest')();
const Configstore = require('configstore');
const pkg = require('../package.json');
const _ = require('lodash');
const CLI = require('clui');
const Spinner = CLI.Spinner;
const chalk = require('chalk');
const inquirer = require('./inquirer');
const conf = new Configstore(pkg.name);
再添加一个函数,检查访问 token 是否已经存在。我们还添加了一个函数,以便其他库可以访问到 octokit
(GitHub) 相关函数:
...
module.exports = {
getInstance: () => {
return octokit;
},
getStoredGithubToken : () => {
return conf.get('github.token');
},
setGithubCredentials : async () => {
...
},
registerNewToken : async () => {
...
}
}
如果 conf
对象存在并且有 github.token
属性,就表示 token 已经存在。在这里我们把 token 值返回给调用的函数。我们稍后会讲到它。
如果 token 没找到,我们需要获取它。当然了,获取 OAuth token 牵涉到网络请求,对用户来说有短暂的等待过程。借这个机会我们可以看看 clui 这个包,它给控制台应用提供了强化功能,转菊花就是其中一个。
创建一个菊花很简单:
const status = new Spinner('Authenticating you, please wait...');
status.start();
任务完成后就可以停掉它,它就从屏幕上消失了:
status.stop();
提示:你也可以用update
方法动态更新文字内容。当你需要展示进度时这会非常有用,比如显示完成的百分比。
完成 GitHub 认证的代码在这:
...
setGithubCredentials : async () => {
const credentials = await inquirer.askGithubCredentials();
octokit.authenticate(
_.extend(
{
type: 'basic',
},
credentials
)
);
},
registerNewToken : async () => {
const status = new Spinner('Authenticating you, please wait...');
status.start();
try {
const response = await octokit.authorization.create({
scopes: ['user', 'public_repo', 'repo', 'repo:status'],
note: 'ginits, the command-line tool for initalizing Git repos'
});
const token = response.data.token;
if(token) {
conf.set('github.token', token);
return token;
} else {
throw new Error("Missing Token","GitHub token was not found in the response");
}
} catch (err) {
throw err;
} finally {
status.stop();
}
},
我们一步一步来看:
- 用之前定义的
setGithubCredentials
方法提示用户输入凭证 - 试图获取 OAuth token之前采用 basic authentication
- 尝试注册新的 token
- 如果成功获取了 token,保存到
configstore
- 返回 token
你创建的任何 token,无论是通过人工还是 API,都可以在 这里看到。在开发过程中,你可能需要删除 ginit 的 access token ——可以通过上面的 note
参数辨认—— 以便重新生成。
提示:如果你的 Github 账户启用了双重认证,这个过程会稍微复杂点。你需要请求验证码(比如通过手机短信),然后通过 X-GitHub-OTP
请求头提供该验证码。更多信息请参阅 Github 开发文档
更新下index.js
文件里的run()
函数,看看效果:
const run = async () => {
let token = github.getStoredGithubToken();
if(!token) {
await github.setGithubCredentials();
token = await github.registerNewToken();
}
console.log(token);
}
请注意,如果某个地方出错的话,你会得到一个 Promise
错误,比如输入的密码不对。稍后我们会讲到处理这些错误的方式。
创建仓库
一旦获得了 OAuth token,我们就可以用它来创建远程 Github 仓库了。
同样,我们可以用 Inquirer
给用户提问。我们需要仓库名称、可选的描述信息以及仓库是公开还是私有。
我们用 minimist 从可选的命令行参数中提取名称和描述的默认值。例如:
ginit my-repo "just a test repository"
这样就设置了默认名称为my-repo
,默认描述为just a test repository
下面这行代码把参数放在一个数组里:
const argv = require('minimist')(process.argv.slice(2));
// { _: [ 'my-repo', 'just a test repository' ] }
提示:这里只展示了 minimist 功能的一点皮毛而已。你还可以用它来解析标志位参数、开关和键值对。更多功能请查看它的文档。
接下来我们加上解析命令行参数的代码,并向用户提出一系列问题。首先更新lib/inquirer.js
文件,在askGithubCredentials
函数后面加上以下代码:
...
askRepoDetails: () => {
const argv = require('minimist')(process.argv.slice(2));
const questions = [
{
type: 'input',
name: 'name',
message: 'Enter a name for the repository:',
default: argv._[0] || files.getCurrentDirectoryBase(),
validate: function( value ) {
if (value.length) {
return true;
} else {
return 'Please enter a name for the repository.';
}
}
},
{
type: 'input',
name: 'description',
default: argv._[1] || null,
message: 'Optionally enter a description of the repository:'
},
{
type: 'list',
name: 'visibility',
message: 'Public or private:',
choices: [ 'public', 'private' ],
default: 'public'
}
];
return inquirer.prompt(questions);
},
接着创建lib/repo.js
文件,加上这些代码:
const _ = require('lodash');
const fs = require('fs');
const git = require('simple-git')();
const CLI = require('clui')
const Spinner = CLI.Spinner;
const inquirer = require('./inquirer');
const gh = require('./github');
module.exports = {
createRemoteRepo: async () => {
const github = gh.getInstance();
const answers = await inquirer.askRepoDetails();
const data = {
name : answers.name,
description : answers.description,
private : (answers.visibility === 'private')
};
const status = new Spinner('Creating remote repository...');
status.start();
try {
const response = await github.repos.create(data);
return response.data.ssh_url;
} catch(err) {
throw err;
} finally {
status.stop();
}
},
}
根据获取的信息,我们就可以利用 Github 包 创建仓库了,它会返回新创建的仓库 URL。然后我们就可以把这个地址设置为本地仓库的 remote。不过还是先新建一个.gitignore
文件吧。
创建 .gitignore 文件
下一步我们将要创建一个简单的“向导”命令行,用来生成 .gitignore
文件。如果用户在已有项目路径里运行我们的应用程序,我们给用户列出当前工作目录的文件和目录, 以让他们选择忽略哪些。
Inquirer 提供的 checkbox
输入类型就是用来做这个的。
![](http://upload-images.jianshu.io/upload_images/1618526-2e12d56a10340bdc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/652/format/webp)
首先我们需要做的就是扫描当前目录,忽略.git
文件夹和任何现有的 .gitignore
文件。我们用 lodash 的 without 方法来做:
const filelist = _.without(fs.readdirSync('.'), '.git', '.gitignore');
如果没有符合条件的结果,就没必要继续执行了,直接 touch
当前的.gitignore
文件并退出函数。
if (filelist.length) {
...
} else {
touch('.gitignore');
}
最后,我们用 Inquirer’s 的 checkbox 列出所有文件。在 lib/inquirer.js
加上如下代码:
...
askIgnoreFiles: (filelist) => {
const questions = [
{
type: 'checkbox',
name: 'ignore',
message: 'Select the files and/or folders you wish to ignore:',
choices: filelist,
default: ['node_modules', 'bower_components']
}
];
return inquirer.prompt(questions);
},
..
请注意,我们也可以提供默认忽略列表。在这里我们预先选择了 node_modules
和 bower_components
目录,如果存在的话。
有了 Inquirer 的代码,现在我们可以写 createGitignore()
函数了。在 lib/repo.js
文件里插入这些代码:
...
createGitignore: async () => {
const filelist = _.without(fs.readdirSync('.'), '.git', '.gitignore');
if (filelist.length) {
const answers = await inquirer.askIgnoreFiles(filelist);
if (answers.ignore.length) {
fs.writeFileSync( '.gitignore', answers.ignore.join( '\n' ) );
} else {
touch( '.gitignore' );
}
} else {
touch('.gitignore');
}
},
...
一旦用户确认,我们把选中的文件列表用换行符拼接起来,写入 .gitignore
文件。 有了.gitignore
文件,可以初始化 Git 仓库了。
应用程序中的 Git 操作
操作 Git 的方法有很多,最简单的可能就是使用 simple-git 了。它提供一系列的链式方法运行 Git 命令。
我们用它来自动化的重复性任务有这些:
- 运行
git init
- 添加
.gitignore
文件 - 添加工作目录的其余内容
- 执行初次 commit
- 添加新创建的远程仓库
- push 工作目录到远端
在 lib/repo.js
中插入以下代码:
...
setupRepo: async (url) => {
const status = new Spinner('Initializing local repository and pushing to remote...');
status.start();
try {
await git
.init()
.add('.gitignore')
.add('./*')
.commit('Initial commit')
.addRemote('origin', url)
.push('origin', 'master');
return true;
} catch(err) {
throw err;
} finally {
status.stop();
}
},
...
全部串起来
首先在 lib/github.js
中写几个 helper 函数。一个用来方便地存取 token,一个用来建立 oauth
认证:
...
githubAuth : (token) => {
octokit.authenticate({
type : 'oauth',
token : token
});
},
getStoredGithubToken : () => {
return conf.get('github.token');
},
...
接着在 index.js
里写个函数用来处理获取 token 的逻辑。在run()
函数前加入这些代码:
const getGithubToken = async () => {
//从 config store 获取 token
let token = github.getStoredGithubToken();
if(token) {
return token;
}
// 没找到 token ,使用凭证访问 GitHub 账号
await github.setGithubCredentials();
// 注册新 token
token = await github.registerNewToken();
return token;
}
最后,更新run()
函数,加上应用程序主要逻辑处理代码。
const run = async () => {
try {
// 获取并设置认证 Token
const token = await getGithubToken();
github.githubAuth(token);
// 创建远程仓库
const url = await repo.createRemoteRepo();
// 创建 .gitignore 文件
await repo.createGitignore();
// 建立本地仓库并推送到远端
const done = await repo.setupRepo(url);
if(done) {
console.log(chalk.green('All done!'));
}
} catch(err) {
if (err) {
switch (err.code) {
case 401:
console.log(chalk.red('Couldn\'t log you in. Please provide correct credentials/token.'));
break;
case 422:
console.log(chalk.red('There already exists a remote repository with the same name'));
break;
default:
console.log(err);
}
}
}
}
如你所见,在顺序调用其他函数(createRemoteRepo()
, createGitignore()
, setupRepo()
)之前,我们要确保用户是通过认证的。代码还处理了异常,并给予了用户适当的反馈。
让 ginit 命令全局可用
剩下的一件事是让我们的命令行在全局可用。为此,我们需要在index.js
文件顶部加上一行叫 shebang 的代码:
#!/usr/bin/env node
接着在package.json
文件中新增一个 bin
属性。它用来绑定命令名称(ginit
)和对应被执行的文件(路径相对于 package.json
)。
"bin": {
"ginit": "./index.js"
}
然后,在全局安装这个模块,这样一个可用的 shell 命令就生成了。
npm install -g
提示:Windows下也是有效的, 因为 npm 会帮你的脚本安装一个 cmd 外壳程序
更进一步
我们已经做出了一个漂亮却很简单的命令行应用程序用来初始化 Git 仓库。但是你还可以做很多事来进一步加强它。
如果你是 Bitbucket 用户,你可以适配该程序去使用 Bitbucket API 来创建仓库。有个 [Node.js API (https://www.npmjs.com/package/bitbucket-api) 可以帮你起步。你可能希望增加几个命令行选项,或者让用户选择使用 Github 还是 Bitbucket(用 Inquirer 再适合不过了),或者直接把 Github 相关的代码替换成 Bitbucket 对应的代码。
你还可以指定.gitgnore
文件默认列表,这方面 preferences
包比较合适,或者可以提供一些模板—— 可能是让用户选择项目类型。还可以把它集成到 .gitignore.io 。
除此之外,你还可以添加额外的验证、提供跳过某些步骤的功能等等。发挥你的想象力,如果还有其他想法,欢迎留言评论!
作者:空引
链接:https://www.jianshu.com/p/1c5d086c68fa
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
如何用node编写命令行工具,附上一个ginit示例,并推荐好用的命令行工具的更多相关文章
- 如何用Node编写命令行工具
0. 命令行工具 当全局安装模块之后,我们可以在控制台下执行指定的命令来运行操作,如果npm一样.我把这样的模块称之为命令行工具模块(如理解有偏颇,欢迎指正) 1.用Node编写命令行工具 在Node ...
- Logstash 的命令行入门 ( 附上相关实验步骤 )
Logstash 的命令行入门 ( 附上相关实验步骤 ) 在之前的博客中,我们已经在 Macbook Big Sur 环境下安装了 ELK 的相关软件,并且已经可以成功运行对应的模块: 如果没有安装的 ...
- 用node编写自己的cli工具
工作中接到新项目,开发前都需要先规划项目目录,然后一个个创建文件,搭建sass编译环境,下载jquery,Swiper等类库... 这些准备工作都要花上不少时间.每做一个项目,都会遇到同样的问题,再重 ...
- 用node编写cli工具
cli是command-line interface的缩写,即命令行工具,常用的vue-cli, create-react-app, express-generator 等都是cli工具. 本文以自己 ...
- 如何用node开发自己的cli工具
如何用node开发自己的cli工具 灵感 写这个工具的灵感以及场景源于youtube的一次闲聊 github 地址 blog首发 使用场景 原本我们写博客展示shell,例如:安装运转docker,一 ...
- 利用Node.js的Net模块实现一个命令行多人聊天室
1.net模块基本API 要使用Node.js的net模块实现一个命令行聊天室,就必须先了解NET模块的API使用.NET模块API分为两大类:Server和Socket类.工厂方法. Server类 ...
- 构建工具是如何用 node 操作 html/js/css/md 文件的
构建工具是如何用 node 操作 html/js/css/md 文件的 从本质上来说,html/js/css/md ... 源代码文件都是文本文件,文本文件的内容都是字符串,对文本文件的操作其实就是对 ...
- 如何用 Node.js 和 Elasticsearch 构建搜索引擎
Elasticsearch 是一款开源的搜索引擎,由于其高性能和分布式系统架构而备受关注.本文将讨论其关键特性,并手把手教你如何用它创建 Node.js 搜索引擎. Elasticsearch 概述 ...
- node编写自己的cli
用node编写自己的cli工具 工作中接到新项目,开发前都需要先规划项目目录,然后一个个创建文件,搭建sass编译环境,下载jquery,Swiper等类库... 这些准备工作都要花上不少时间.每 ...
随机推荐
- nginx 一键安装
#!/bin/bash1. 关闭防火墙 和 selinux service iptables stopsetenforce 0 2. 判断是否有nginx服务netstat -ntlp |grep n ...
- Vue 变异方法Push的留言板实例
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- A1089 Insert or Merge (25 分)
一.技术总结 看到是一个two pointers问题,核心是要理解插入排序和归并排序的实现原理,然后判断最后实现 可以知道a数组和b数组怎么样判断是插入排序还是归并排序,因为插入排序是来一个排一个,所 ...
- 【转】ServletContext介绍及用法
1.1. 介绍 ServletContext官方叫servlet上下文.服务器会为每一个工程创建一个对象,这个对象就是ServletContext对象.这个对象全局唯一,而且工程内部的所有servl ...
- Python 将numpy array由浮点型转换为整型
Python 将numpy array由浮点型转换为整型 ——使用numpy中的astype()方法可以实现,如:
- python-3-条件判断练习题
前言 我们在前面两章学习了基础数据类型与条件判断语句,今天我们来做下练习题.如果你有不一样的解题思路在评论区亮出你的宝剑!!! 一.习题如下: 1.使用 while 循环输出 1 2 3 4 5 6 ...
- LeetCode 26:删除排序数组中的重复项 Remove Duplicates from Sorted Array
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成. Give ...
- 第三节: List类型的介绍、生产者消费者模式、发布订阅模式
一. List类型基础 1.介绍 它是一个双向链表,支持左进.左出.右进.右出,所以它即可以充当队列使用,也可以充当栈使用. (1). 队列:先进先出, 可以利用List左进右出,或者右进左出(Lis ...
- 使用LocalDateTime计算两个时间的差
LocalDateTime now = LocalDateTime.now();System.out.println("计算两个时间的差:");LocalDateTime end ...
- Neo4j 第十篇:更新数据
更新图包括图的节点和关系的创建.更新和删除,也能更新图的节点和关系的属性.节点标签和关系类型. 一,创建节点 1,创建空的节点 CREATE (n) CREATE (a),(b) 2,创建带标签的节点 ...