用Rust清理eclipse自动升级后的重复插件

  • 1. 简介
  • 2. 创建工程
  • 3. 命令行解析
    • 3.1 引用`clap`库
    • 3.2 创建`Config`结构
    • 3.3 调整`main`代码
    • 3.4 测试效果
  • 4. 版本号识别
    • 4.1 eclipse版本号
    • 4.2 `Version`实现
    • 4.3 模块声明
  • 5. 插件识别
    • 5.1 `Plugin`结构
    • 5.2 解析插件
    • 5.3 插件操作
  • 6. 插件清理
    • 6.1 插件扫描
    • 6.2 执行清理
  • 7. 总结

1. 简介

eclipse自动升级版本之后,在/eclipse/plugins目录仍然会保留旧版本的插件,想要写一个脚本清理插件,正好最近刚学习rust编程,便用rust开发了一个eclipse插件清理工具eclean

本文简单介绍清理工具的开发过程,详细源代码可以在github下载并自行编译:

git clone https://github.com/leexgone/ecleaner.git
cd ./ecleaner
cargo build --release

工具支持清理eclipse升级后plugins目录下的冗余插件。

  • 清理eclipse插件目录并将清理插件备份:
eclean c:/eclipse e:/backup/eclipse
  • 检测eclipse目录下是否含有可清理的插件:
eclean -t c:/eclipse
  • 更多使用命令查阅:
eclean --help

2. 创建工程

使用cargo new elean创建工程,调整Cargo.toml内容并在src目录下创建lib.rs文件。

3. 命令行解析

eclean是一个命令行工具,首先我们需要支持命令行参数的解析。

rust的clap库是一套功能强大的命令行参数解析库,这里我们使用clap解析命令行参数。

3.1 引用clap

Cargo.toml里加入clap依赖:

[dependencies]
clap = "2.33.3"

3.2 创建Config结构

编辑lib.rs代码,定义Config结构存储命令配置信息,使用clap解析命令参数:

use std::{collections::HashMap, error::Error, fs, io::{self, ErrorKind}, path::{Path, PathBuf}, usize};
use std::fmt::Display;use clap::{App, Arg};pub struct Config {dir: String,backup: String,verbose: bool,test: bool,force: bool,
}impl Display for Config {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {write!(f, "[dir = {}, backup = {}, verbose = {}, test = {}, force = {}]", self.dir, self.backup, self.verbose, self.test, self.force)}
}impl Config {pub fn new() -> Result<Config, String> {let matches = App::new("eclean").version("1.1.0").author("Steven Lee <leexgone@163.com>").about("Clean up the duplicated plugins in eclipse plugins directory.").arg(Arg::with_name("DIR").help("The eclipse root directory to be cleaned. The `/plugins` directory should be under this directory.").required(true).index(1)).arg(Arg::with_name("BACKUP").help("Specify a backup directory to store the removed plugins.").required_unless("test").index(2)).arg(Arg::with_name("verbose").short("v").long("verbose").help("Use verbose output")).arg(Arg::with_name("test").short("t").long("test").help("Scan and find the duplicated plugins, but do nothing")).arg(Arg::with_name("force").short("f").long("force").help("Clean up the duplicated plugins automatically. Never prompt.")).get_matches();let dir = matches.value_of("DIR").unwrap();let backup = matches.value_of("BACKUP").unwrap_or("");let verbose = matches.is_present("verbose");let test = matches.is_present("test");let force = matches.is_present("force");let root_path = Path::new(dir);if !root_path.is_dir() {let msg = format!("DIR '{}' does not exist", dir);return Err(msg);}if !test {let backup_path = Path::new(backup);if !backup_path.is_dir() {let msg = format!("BACKUP dir '{}' does not exist", backup);return Err(msg);}}Ok(Config {dir: String::from(dir),backup: String::from(backup),verbose,test,force,})}
}

Config结构存储了用户参数命令的配置信息:

  • dir:eclipse目录(必须)
  • backup: 清理插件备份目录(必须,在test模式下可以忽略)
  • verbose:输出详细日志
  • test:仅检测eclipse插件目录,不执行清理操作
  • force:不询问用户强制清理插件

这里我们对用户的输入进行了检测,在目录不存在提示错误;而必须参数的校验则通过clap完成即可。

3.3 调整main代码

main.rs中加入参数命令的解析代码:

use std::process;use eclean::Config;fn main() {let  config = Config::new().unwrap_or_else(|err| {eprintln!("Error when parsing arguments: {}.", err);process::exit(1);});// ...
}

3.4 测试效果

PS E:\GitHub\ecleaner> cargo buildFinished dev [unoptimized + debuginfo] target(s) in 0.14s
PS E:\GitHub\ecleaner> .\target\debug\eclean.exe --help
eclean 1.1.0
Steven Lee <leexgone@163.com>
Clean up the duplicated plugins in eclipse plugins directory.USAGE:eclean.exe [FLAGS] <DIR> <BACKUP>FLAGS:-h, --help       Prints help information-t, --test       Scan and find the duplicated plugins, but do nothing-V, --version    Prints version information-v, --verbose    Use verbose outputARGS:<DIR>       The eclipse root directory to be cleaned. The `/plugins` directory should be under this directory.<BACKUP>    Specify a backup directory to store the removed plugins.
PS E:\GitHub\ecleaner> .\target\debug\eclean.exe d:/eclipse
error: The following required arguments were not provided:<BACKUP>USAGE:eclean.exe [FLAGS] <DIR> <BACKUP>For more information try --help
PS E:\GitHub\ecleaner>

4. 版本号识别

4.1 eclipse版本号

我们观察eclipse插件的版本号,由4部分构成:major.minor.patch.build:

  • major:主版本号
  • minor:次版本号
  • patch: 补丁版本号
  • build:构建版本号,可忽略

例如:org.apache.axis_1.4.0.v201411182030,org.eclipse.core.runtime_3.20.0.v20201027-1526.jar。

这里,我们设计Version结构存储版本信息,因为部分插件没有提供build版本号,我们使用Option<String>存储。

pub struct Version {pub major: usize,pub minor: usize,pub patch: usize,pub build: Option<String>,
}

4.2 Version实现

在清理插件时,我们需要对多个版本的插件版本进行排序,这里我们通过Version实现排序trait来实现。

为支持排序,Version结构体需要实现EqOrdPartialEqPartialOrd四个trait。

Version的解析方法比较简单,通过字符串的分隔即可实现,这里我们仅实现版本号字符串的解析,整体插件名称的解析在后面处理。

我们在工程里增加version.rs文件,编写Version结构体代码:

use std::{error::Error, fmt::Display};#[derive(Debug)]
#[derive(Eq)]
pub struct Version {pub major: usize,pub minor: usize,pub patch: usize,pub build: Option<String>,
}impl Version {pub fn new(major: usize, minor: usize, patch: usize, build: Option<&str>) -> Version {Version {major,minor,patch,build: if let Some(text) = build {Some(String::from(text))} else {None}}}pub fn parse(expr: &str) -> Result<Version, Box<dyn Error>> {let mut version = Version {major: 0,minor: 0,patch: 0,build: None,};for (i, val) in expr.split('.').enumerate() {match i {0 => {version.major = val.parse()?;}1 => {version.minor = val.parse()?;}2 => {version.patch = val.parse()?;}3 => {version.build = Some(String::from(val));}_ => {}}};Ok(version)}
}impl Display for Version {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {if let Some(suffix) = self.build.as_ref() {write!(f, "{}.{}.{}.{}", self.major, self.minor, self.patch, suffix)} else {write!(f, "{}.{}.{}", self.major, self.minor, self.patch)}}
}impl Ord for Version {fn cmp(&self, other: &Self) -> std::cmp::Ordering {let ret = self.major.cmp(&other.major);if ret != std::cmp::Ordering::Equal {return ret;}let ret = self.minor.cmp(&other.minor);if ret != std::cmp::Ordering::Equal {return ret;}let ret = self.build.cmp(&other.build);if ret != std::cmp::Ordering::Equal {return ret;}let self_build = if let Some(build) = self.build.as_ref() {build} else {""};let other_build = if let Some(build) = other.build.as_ref() {build} else {""};self_build.cmp(other_build)}
}impl PartialOrd for Version {fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {Some(self.cmp(other))}
}impl PartialEq for Version {fn eq(&self, other: &Self) -> bool {self.cmp(other) == std::cmp::Ordering::Equal}
}

4.3 模块声明

因为我们引入了一个新的源代码文件version.rs,为保证编译通过,需要在lib.rs文件开头增加如下声明:

mod version;

5. 插件识别

eclipse的插件有两种形式:

  • 以目录形式打包的插件,目录名类似org.apache.ant_1.10.9.v20201106-1946
  • 以JAR包形式打包的插件,文件名类似org.eclipse.core.runtime_3.20.0.v20201027-1526.jar

所有的插件命名以plugin-name_version-expr的形式命名,为进行插件的扫描和比较,我们需要记录插件的标识和版本号;此外,还需要记录插件的目录或文件名,以用于后续的清理操作。

5.1 Plugin结构

创建plugin.rs源代码文件,声明Plugin结构体:

pub struct Plugin {pub path: PathBuf,pub name: String,pub version: Version,
}

lib.rs中增加plugin模块声明:

mod plugin;

5.2 解析插件

要解析插件的名称和版本号,需要将插件文件名(目录名)拆分,因为插件名称和版本号中都可能含有_符号,这里不能简单的使用字符分隔。

在这里我们需要使用正则表达式进行识别,首先引入rust的regex库,在Cargo.toml里增加插件引用:

[dependencies]
regex = "1.4.3"

然后修改plugin.rs中的代码,Plugin::new()函数实现解析代码,解析时我们需要区分目录和文件的插件形式。

use std::{error::Error, fmt::Display, fs, io::ErrorKind, path::PathBuf, usize};use regex::Regex;use super::version::Version;#[derive(Debug)]
pub struct Plugin {pub path: PathBuf,pub name: String,pub version: Version,
}impl Display for Plugin {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {write!(f, "{}({})", self.name, self.version)}
}macro_rules! return_err {($msg:expr) => {{let e = std::io::Error::new(ErrorKind::Other, $msg);return Err(Box::new(e));}}
}impl Plugin {pub fn new(path: PathBuf) -> Result<Plugin, Box<dyn Error>> {let filename: String;let name = if path.is_file() {path.file_stem()} else {path.file_name()};match name {Some(stem) => {filename = String::from(stem.to_str().unwrap());},None => {return_err!(format!("Error parsing plugin: {}", path.display()));}}let regex = Regex::new("_\\d+[.]\\d+[.]\\d+")?;let (name, version) = if let Some(m) = regex.find(&filename) { let plugin_name = &filename[0..m.start()];let version_expr = &filename[m.start() + 1..];match Version::parse(version_expr) {Ok(version) => {(String::from(plugin_name),version,)},Err(e) => {return_err!(format!("Error parsings plugin `{}`: {}", path.display(), e));}}} else {(filename, Version::new(0, 0, 0, None))};Ok(Plugin {path,name,version})}
}

其中,我们定义了return_err宏简化异常处理

5.3 插件操作

重复插件清理时,需要将插件从/eclipse/plugins目录移动到备份目录中,我们为Plugin结构体提供move_to()方法实现插件迁移。由于插件可能是目录形式,使用递归的方式进行移动。

impl Plugin {pub fn move_to(&self, target: &PathBuf) -> Result<usize, Box<dyn Error>> {let count = Plugin::copy_all(&self.path, target)?;self.remove()?;Ok(count)}fn copy_all(root: &PathBuf, target: &PathBuf) -> Result<usize, Box<dyn Error>> {let mut count: usize = 0;let mut dest_path = target.clone();dest_path.push(root.file_name().unwrap());if root.is_file() {fs::copy(&root, &dest_path)?;count += 1;} else if root.is_dir() {if !dest_path.exists() {fs::create_dir(&dest_path)?;}for entry in root.read_dir()? {let entry = entry?;let sub_path = entry.path();count += Plugin::copy_all(&sub_path, &dest_path)?;}}Ok(count)}fn remove(&self) -> Result<(), Box<dyn Error>> {if self.path.is_file() {fs::remove_file(&self.path)?;} else if self.path.is_dir() {fs::remove_dir_all(&self.path)?;}Ok(())}
}

6. 插件清理

6.1 插件扫描

要清理重复的插件,首先需要对eclipse的所有插件进行扫描,我们定义PluginSet结构体存储插件并封装清理操作,使用哈希表来存储扫描后的插件。

修改lib.rs代码,增加PluginSet定义与实现:


macro_rules! log {($enabled:expr) => {{if $enabled { println!(); }}};($enabled:expr, $($arg:tt)*) => {{if $enabled { println!($($arg)*); }}};
}#[derive(Debug)]
struct PluginSet {plugins: HashMap<String, Vec<Plugin>>,
}impl PluginSet {fn new(dir: &str, verbose: bool) -> Result<PluginSet, Box<dyn Error>> {let plugin_path = PathBuf::from(format!("{}/plugins", dir));if !plugin_path.is_dir() { let e = std::io::Error::new(ErrorKind::NotFound, format!("Can not find `plugins` dir under `{}` dir", dir));return Err(Box::new(e));}let mut plugins: HashMap<String, Vec<Plugin>> = HashMap::new();log!(verbose, "Search plugins under dir `{}`...", plugin_path.display());for entry in plugin_path.read_dir()? {let entry = entry?;let path = entry.path();let plugin = Plugin::new(path)?;log!(verbose, ">> {}", plugin);if let Some(list) = plugins.get_mut(&plugin.name) {list.push(plugin);} else {plugins.insert(plugin.name.clone(), vec![plugin]);}}for list in plugins.values_mut() {list.sort_by(|a, b| a.version.cmp(&b.version));}Ok(PluginSet { plugins })}fn find_duplicates(&self) -> Vec<&Vec<Plugin>> {self.plugins.values().filter(|list| list.len() > 1).collect()}fn print_dupicates(duplicates: &Vec<&Vec<Plugin>>) {println!("{} duplicated plugins found:", duplicates.len());for (i, list) in duplicates.iter().enumerate() {let id = i + 1;let plugins = *list;let keep = plugins.last().unwrap();print!("  {}\t{} [KEEP: {}; DISCARD: ", id, keep.name, keep.version);for (p, plugin) in plugins.iter().enumerate() {if p == plugins.len() - 1 {break;}if p > 0 {print!(", ");}print!("{}", plugin.version);}println!("]");}}fn remove_duplicates(duplicates: &Vec<&Vec<Plugin>>, backup: &str, verbose: bool) -> Result<(), Box<dyn Error>> {let backup_path: PathBuf = [backup, "plugins"].iter().collect();if !backup_path.exists() {fs::create_dir(&backup_path)?;log!(verbose, "Create backup dir: {}", backup_path.display());}let mut count = 0;for list in duplicates {let plugins = *list;let keep = plugins.last().unwrap();log!(verbose, "Cleaning up `{}`, lastest: v{}...", keep.name, keep.version);for (i, plugin) in plugins.iter().enumerate() {if i == plugins.len() - 1 {break;}let file_count = plugin.move_to(&backup_path)?;log!(verbose, "  remove version v{}, {} files deleted.", plugin.version, file_count);count += 1;}}println!("{} plugins have been cleaned up successfully!", count);Ok(())}
}

其中:

  • new()方法扫描指定的eclipse目录,识别所有插件并创建PluginSet
  • find_duplicates()方法过滤存在重复版本的插件列表
  • print_dupicates()方法打印存在重复版本的插件信息
  • remove_duplicates()方法执行清理,将旧版本插件移动到备份目录中

6.2 执行清理

lib.rs中增加插件清理方法:

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {let plugin_set = PluginSet::new(&config.dir, config.verbose)?;let duplicates = plugin_set.find_duplicates();if duplicates.is_empty() {println!("There are no duplidated plugins.")} else {PluginSet::print_dupicates(&duplicates);if !config.test {if config.force || prompt(duplicates.len()) {PluginSet::remove_duplicates(&duplicates, &config.backup, config.verbose)?;}}}Ok(())
}fn prompt(size: usize) -> bool {println!("{} plugins will be removed to the backup dir. Continue to remove these plugins? [Y/n] ", size);let mut answer = String::new();io::stdin().read_line(&mut answer).expect("Invalidate input");let answer = answer.trim();"Y".eq_ignore_ascii_case(answer) || "YES".eq_ignore_ascii_case(answer)
}

最后,我们调整main.rs,完成所有实现:

use std::process;use eclean::Config;fn main() {let  config = Config::new().unwrap_or_else(|err| {eprintln!("Error when parsing arguments: {}.", err);process::exit(1);});if let Err(e) = eclean::run(config) {eprintln!("Error when cleaning up: {}", e);process::exit(2);}
}

7. 总结

我们使用rust开发了一个eclean工具,开发过程中使用了clap和regex库。

rust语言学习入门有一定的难度,但掌握之后开发效率还是比较高效的,编译的程序体积小又易于跨平台使用,还是一门很不错的语言的。

用Rust清理eclipse自动升级后的重复插件相关推荐

  1. chrome升级后LODOP打印插件无法使用

    原文地址为: chrome升级后LODOP打印插件无法使用 今天帮朋友使用LODOP实现一个套打程序时,发现LODOP打印插件在chrome下始终无法使用.分析后发现是自己才升级了chrome,chr ...

  2. java打印插件lodop_chrome升级后LODOP打印插件无法使用

    JZOJ 1312:关灯问题 传送门 少见的DP再DP题目.题面不短,但是可以看出来这是一道DP题.而且正解的算法复杂度应该是$O(N^3)$.而且给了部分$O(N^4)$的算法的分.可以看出来要AC ...

  3. Android 实现应用升级方案(暨第三方自动升级服务无法使用后的解决方案)

    第三方推送升级服务不再靠谱: 以前在做Android开发的时候,在应用升级方面都是使用的第三方推送升级服务,但是目前因为一些非技术性的问题,一些第三方厂商不再提供自动升级服务,比如友盟,那么当第三方推 ...

  4. Chrome 谷歌浏览器升级后不再自动保存账号名和密码

    Chrome 谷歌浏览器升级后不再自动保存账号名和密码 1.检查设置:是否开启自动保存密码设置 设置>密码:开启保存密码 2.如果设置没有问题,依然不能保存密码,进行如下操作: 关闭浏览器: 删 ...

  5. android eclipse自动更新,Android Eclipse 升级ADT到24.0.2完美解决方案

    由于在线下载更新奇慢无不,所以会出现"假死"的状态,具有的网友说下了整整一晚上(8小时+)才下载完成,所以在线下载ADT工具可以说是相当不靠谱的.故我下面给大家介绍一种离线下载AD ...

  6. C#Winform程序如何发布并自动升级(图解)

    有不少朋友问到C#Winform程序怎么样配置升级,怎么样打包,怎么样发布的,在这里我解释一下打包和发布 关于打包的大家可以看我的文章C# winform程序怎么打包成安装项目(图解) 其实打包是打包 ...

  7. OkHttpUtils-2.0.0 升级后改名 OkGo,全新完美支持 RxJava,比 Retrofit 更简单易用。

    okhttp-OkGo 项目地址:jeasonlzy/okhttp-OkGo 简介:OkHttpUtils-2.0.0 升级后改名 OkGo,全新完美支持 RxJava,比 Retrofit 更简单易 ...

  8. Winform 自动升级程序

    抽时间整理下升级这块的功能,并封装一个升级工具包. 作为winform 程序员都有一个C/S端程序绕不过的问题.那就是如何升级程序? 程序升级两种1.启动时强制更新 2.自动.手动获取更新,并确认是否 ...

  9. 禁止暴风影音的自动升级的方法

    禁止暴风影音的自动升级的方法: 1.控制面板–> 管理工具–> 服务,找到"Contrl Center of Storm Media",双击,在服务状态下点" ...

最新文章

  1. 02Django入门仪式之Hello World
  2. 开源监控软件ganglia安装手册
  3. IntelliJ Idea 常用快捷键 列表
  4. 如何更改ubuntu的用户密码
  5. 13 代码分割之import静动态导入
  6. php置顶文章,zblogphp调用置顶文章的教程
  7. tomcat中间件的默认端口号_死磕Tomcat系列(1)——整体架构
  8. 在mysql中如何添加函数库_详细讲解如何为MySQL数据库添加新函数
  9. 自定义NodeJS-C++ Addons使用说明
  10. 文件编辑vim常用命令
  11. visio2007安装教程_Office Visio 2007如何安装-Microsoft Office Visio 2007安装步骤
  12. K2P加USB口刷入Padavan
  13. POST 请求的四种提交数据方式
  14. win7无法连接打印机拒绝访问_小编告诉你打印机拒绝访问无法连接
  15. No1.初来乍到,请多指教
  16. 安卓移动应用开发之从零开始写安卓小程序
  17. 商业需求文档该如何去写?
  18. 在一家虚拟现实公司工作是什么感受?
  19. 打卡网红IP品牌奶茶店,好喝又好看的茶天娜
  20. 机器学习coursera 第三章编程作业

热门文章

  1. android NFC读写卡教程
  2. 六、Abp Vnext 中Efcore的多模块关联查询
  3. 移动端/PC端网页开发建议
  4. 年产10000吨即食型大头菜工厂设计
  5. 如何在网站上加入google地图 .net (by yfdong22)
  6. PHP sort() 函数等
  7. 使用phpredis批量向sort set中加入元素
  8. linux搭建ftp服务并创建目录,linux搭建ftp服务器
  9. 授人以鱼不如授人以渔,今天手把手教你怎么从微软官方下载Windos10正版镜像
  10. 关于Authorware和文本格式