百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

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

bigegpt 2024-08-07 17:56 2 浏览

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 build
    Finished 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 output

ARGS:
    <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.axis1.4.0.v201411182030,org.eclipse.core.runtime3.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语言学习入门有一定的难度,但掌握之后开发效率还是比较高效的,编译的程序体积小又易于跨平台使用,还是一门很不错的语言的。

相关推荐

php-fpm的配置和优化

目录概述php-fpm配置php-fpm进程优化配置慢日志查询配置php7进阶到架构师相关阅读概述这是关于php进阶到架构之php7核心技术与实战学习的系列课程:php-fpm的配置和优化学习目标:理...

成功安装 Magento2.4.3最新版教程「技术干货」

外贸独立站设计公司xingbell.com经过多次的反复实验,最新版的magento2.4.3在oneinstack的环境下的详细安装教程如下:一.vps系统:LinuxCentOS7.7.19...

十分钟让你学会LNMP架构负载均衡

业务架构、应用架构、数据架构和技术架构一、几个基本概念1、pv值pv值(pageviews):页面的浏览量概念:一个网站的所有页面,在一天内,被浏览的总次数。(大型网站通常是上千万的级别)2、u...

php从远程URL获取(mp4 mp3)音视频的流媒体数据

/***从远程URL中获取媒体(如mp4mp3)的内容*@parammixed$file_url*@parammixed$media_type...

Zabbix5.0安装部署

全盘展示运行状态,减轻运维人员的重复性工作量,提高系统排错速度,加速运维知识学习积累。1.png1、环境安装关闭SELinux并重启系统2.png安装httpd、mariadb、php运行yum-...

php 常见配置详解

以下是PHP常见的配置项及其含义:error_reporting:设置错误报告级别,可以控制PHP显示哪些错误。例如,设置为E_ALL将显示所有错误,而设置为0将禁止显示任何错误。displa...

实践分享|基于基石智算 DeepSeek API + WordPress 插件自动生成访客回复

基石智算举办的DeepSeek案例大赛汇集了不少基于CoresHubDeepSeekAPI服务或模型部署服务的精彩实践。本次我们将分享个人实践:通过DeepSeekAPI+Word...

如何在Eclipse中搭建Zabbix源码的调试和开发环境

Zabbix是一款非常优秀的企业级软件,被设计用于对数万台服务器、虚拟机和网络设备的数百万个监控项进行实时监控。Zabbix是开放源码和免费的,这就意味着当出现bug时,我们可以很方便地通过调试源码来...

MySQL自我保护参数

#头条创作挑战赛#之前(MySQL自我保护工具--pt-kill)提到用pt-kill工具来kill相关的会话,来达到保护数据库的目的,本文再通过修改数据库参数的方式达到阻断长时间运行的SQL的目...

Python闭包深度解析:掌握数据封装的高级技巧

闭包作为Python高级编程特性之一,为开发者提供了一种优雅的方式来实现数据封装和状态保持。这一概念源于函数式编程理论,在现代Python开发中发挥着重要作用。理解和掌握闭包的使用不仅能够提升代码的表...

Java服务网格故障注入与熔断实战

在分布式系统的高可用性挑战中,服务网格的故障注入与熔断机制是检验系统韧性的终极试金石。以下是10道逐步升级的"地狱关卡",每个关卡都对应真实生产环境中可能遇到的致命场景,并附具体场景示...

MySQL数据库性能优化全攻略:程序员必知的七大核心策略

作为程序员,我们每天都要与数据库打交道。当系统用户量突破百万级时,数据库往往成为性能瓶颈的首要怀疑对象。本文将深入探讨MySQL优化的七大核心策略,并提供可直接落地的优化方案,助您构建高效稳定的数据库...

如何在 Windows 11 上使用单个命令安装 XAMPP

XAMPP是一种广泛使用的软件,用于在Windows操作系统上快速运行LAMP服务器包,包括Windows11。尽管LAMP通常用于Linux系统,但XAMPP并不使用Li...

uTorrent怎样将bt种子转换为磁力

如何用uTorrent把BT种子转为磁力链接?以下方法希望能帮到你。1、在uTorrent窗口里,点击工具栏的按钮,所示。2、在打开窗口里,选取要转为磁力的种子文件,然后点击打开按钮,参照图示操作...

支持向量机SVM 分类和回归的实例

支持向量机(SupportVectorMachine)是Cortes和Vapnik于1995年首先提出的,它在解决小样本、非线性及高维模式识别中表现出许多特有的优势,并能够推广应用到函数拟合等其他...