序
本期写一个小项目,rust实现npm init,创建一个package.json文件。作为《rust学习之旅》,第一个练手项目。
基本功能
首先我们分析一下npm init功能做了什么?
-
1、执行
npm init出现了以下提示 -
2、要求我们输入基本信息
-
3、完成创建json文件
{ "name": "npm-init", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" }
功能实现
我们知道了npm init做了些什么,我们也要按照,上面的步骤一步一步来完成我们第一个rust小项目
创建一个npm-init项目
我们使用cargo new 创建一个项目。
cargo new npm-init
接下来我们就可以开整。
输出提示信息
第一步我们先实现npm init的第一步输出提示
作为一个前端er,第一步输出肯定是创建一个console模块。当然这个啥也没干,就是将println包装了一层。😄
pub mod console {
pub fn log(msg: &str) {
println!("{}", msg)
}
pub fn warn() {}
pub fn error() {}
pub fn success() {}
}
接下来我们就可以输出提示信息了
use npm_init::{console, tips};
fn main() {
const DEFAULT_TIP: &str = "This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.";
console::log(DEFAULT_TIP);
}
好的我们完美的完成了第一步。是不是超级厉害(其实这里什么也没有写🤔)。
执行cargo run:
是不是和我们npm int 第一步一样了。
获取输入信息
第二步我们需要获取到用户的输入,我们重构一下刚才的代码。将刚才那个提示信息,放到模块tips中来。并实现了一个函数get_input.
pub mod tips {
use std::io;
pub const DEFAULT_TIP: &str = "This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help init` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.";
pub fn get_input(msg: &str, default: &str) -> String {
println!("{}{}:", msg, format!("({})", default));
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
input = input.replace("\n", "");
if input == "".to_owned() {
default.to_string()
} else {
input
}
}
我们利用io::stdin().read_line来获取用户的输入。并返回回去。我们贴心的支持传递了默认数据。
use npm_init::{console, tips};
fn main() {
console::log(tips::DEFAULT_TIP);
let name = tips::get_input("package name:", "npm-init");
let version = tips::get_input("version:", "1.0.0)");
let description = tips::get_input("description", "");
let entry = tips::get_input("entry point: ", "index.js");
let test_command = tips::get_input(
"test command",
"echo \"Error: no test specified\" && exit 1",
);
let git_repo = tips::get_input("git repository:", "https://github.com/kinfuy/npm-init.git");
let keyword = tips::get_input("keywords", "");
let author = tips::get_input("author", "");
let license = tips::get_input("license", "ISC");
let is_create = tips::get_input("Is this OK?", "yes");
}
现在我们获取到了用户的输入,取得了package.json初始化需要的字段信息。
但是这里我们只是获取到了数据,我们得找个地方储存一下,这样直接放到文件里,显得太呆了
我们创建一个模块npm并暴露了结构体Package,注意我们在【Rust学习之旅】模块管理:包、crate、模块、path(七)中讲到的,模块中结构体中独立的字段需要单独设置pub.
pub mod npm {
pub struct Script {
pub test: String,
}
pub struct Package {
pub name: String,
pub version: String,
pub description: String,
pub entry: String,
pub script: Script,
pub git_repo: String,
pub keyword: String,
pub author: String,
pub license: String,
pub is_create: String,
}
}
结合上面的代码,我们可以直接将获取到的信息打印在控制台上
fn main() {
console::log(tips::DEFAULT_TIP);
let name = tips::get_input("package name:", "npm-init");
let version = tips::get_input("version:", "1.0.0)");
let description = tips::get_input("description", "");
let entry = tips::get_input("entry point: ", "index.js");
let test_command = tips::get_input(
"test command",
"echo \"Error: no test specified\" && exit 1",
);
let git_repo = tips::get_input("git repository:", "https://github.com/kinfuy/npm-init.git");
let keyword = tips::get_input("keywords", "");
let author = tips::get_input("author", "");
let license = tips::get_input("license", "ISC");
let is_create = tips::get_input("Is this OK?", "yes");
let pkg = Package {
name,
version,
description,
entry,
script: Script { test: test_command },
git_repo,
keyword,
author,
license,
is_create,
};
println!("{:#?}", pkg);
}
注意打印结构体需要为结构体派生 Debug trait
通过
#[derive]属性,编译器能够提供某些 trait 的基本实现。如果需要更复杂的行为,这些 trait 也可以手动实现。
#[derive(Debug)]
pub struct Script {
pub test: String,
}
#[derive(Debug)]
pub struct Package {
pub name: String,
pub version: String,
pub description: String,
pub entry: String,
pub script: Script,
pub git_repo: String,
pub keyword: String,
pub author: String,
pub license: String,
pub is_create: String,
}
至此我们的第二步也成功完成了
输出到文件
最后一步我们将获取到的数据输出到packages.json,这里我们需要将结构体数据转化为json.
在 Rust 中,如果想要将一个结构体转换为 JSON 字符串,你可以使用 serde 库。
首先,你需要在 Cargo.toml 文件中添加 serde 和 serde_json 作为依赖:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
接下来我们继续改造一下我们的npm模块
pub mod npm {
use serde::{Deserialize, Serialize};
use std::fs;
#[derive(Debug, Serialize, Deserialize)]
pub struct Script {
pub test: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Package {
pub name: String,
pub version: String,
pub description: String,
pub entry: String,
pub script: Script,
pub git_repo: String,
pub keyword: String,
pub author: String,
pub license: String,
pub is_create: String,
}
pub trait Output {
/// 输出到package.json
fn output(&self) -> Result<(), io::Error>
where
Self: Serialize;
}
impl Output for Package {
fn output(&self) -> Result<(), std::io::Error>
where
Self: Serialize,
{
let serialized = serde_json::to_string(&self).unwrap();
fs::write("package.json", serialized)?;
Ok(())
}
}
}
我们定义 Output trait并为Package实现 output方法。
我们为结构体派生 Serialize, Deserialize 这两个第三方提供的trait后,我们就可以使用
serde_json::to_string() 讲结构体序列化。
接下来我们fs::write 就可以将我们的结构体输出到文件了。
在main方法中调用即可。
pkg.output().unwrap();
结语
这个小项目虽然小,我们也使用到了很多《rust学习之旅》中讲到了很多知识点
结构体的创建使用trait的创建并为结构体实现impl- 处理错误,
Result<T,U>枚举类型的使用 - 怎么使用第三方库
fs::write将数据写入文件mod模块化的使用- 了解
#[derive]派生
这个小项目,还有很多瑕疵,例如:没有处理输入参数的校验等,有兴趣的小伙伴可以自己尝试看看
相关链接
- 源码:npm-init