首页 > 其他分享 >用kotlin来开发一个cli工具 | 没用的技能+1

用kotlin来开发一个cli工具 | 没用的技能+1

时间:2023-06-19 10:34:20浏览次数:55  
标签:cli kotlin jar project File file 没用 脚手架 main


脚手架

脚手架是为了保证各施工过程顺利进行而搭设的工作平台

而在程序开发过程中,每个工程或者说公司也都需要一个脚手架工具。通过脚手架命令行的形式简化开发流程,避免发生一些人为的相对低级的问题,所以这个也就是为什么叫做脚手架的原因吧。

而由于每个公司的代码规范都不同,一般情况下会主动让开发同学进行工程方面的cv操作,就是成本高并且容易出错。这也就是为什么我们打算写一些这样的工具的原因。

在一般情况下,更多的程序猿会选择用python去写,因为脚本语言的灵活性,但是对于一个辣鸡安卓来说会增加额外的学习成本,所以这就取决于有没有天赋了,能不能对一门陌生的语言快速上手了。

这次文章会介绍的是用kotlin去构建一个二进制文件,通过这个来完成脚手架cli工具的建设。

开搞

demo 工程地址TheNext

一开始的启发在于有时候使用一些第三方工具的时候会提供一个jar包,然后只要输入java -jar xxx.jar就可以使用这个jar包中的Main函数了。

因为是一个jar包,所以里面的内容肯定也都是用jvm内的几种语言来进行编写的,那么这就让我们这种老年选手看到了一丝丝的希望。

开发调试

先建立了一个java工程,然后构建了一个main函数,之后开始进行代码编写。但是如果每次都需要先打包之后在通过java -jar来执行的话非常不便利开发并且debug。而且模拟入参也灰常的恶心,你也知道的程序猿都是懒人吗。

所以我们就借用了unittest的能力,对于入参进行mock进行简单的调试功能了。

【参考地址](github.com/Leifzhang/T…)

class Sample {

    @Test
    fun help() {
        Next.main(
            arrayOf(
                "--help"
            )
        )
    }

    @Test
    fun testAndroidModule() {
        val file = File("")
        val moduleName = "strike-freedom"
        val groupName = "com.kronos.common"
        Next.main(
            arrayOf(
                "module", "android",
                "-file", file.absolutePath,
                "-name", moduleName,
                "-group", groupName
            )
        )
    }

    @Test
    fun testAndroidApplication() {
        val file = File("../app/")  
        val projectName = "freedom"
        Next.main(
            arrayOf(
                "project", "android",
                "-name", projectName,
                "-file", file.absolutePath
            )
        )
    }

}

此处我们将Main函数通过unittest来进行模拟,这样就可以方便我们在开发阶段快速调试脚手架的能力了。

每个方法块都可以认为是一个运行的入口,通过这个来模拟出程序所需要的入参。从而一边完成了测试代码的编写,一边完成了调试入口。

jcommander

这是一个让我们可以更像模像样的写一个cli的入参解析工具,即使参数顺序是错乱的,我们仍然能解析出我们想要的数据结构,让我们的工程看起来更正规一点。而且这个库也被很多开源项目所使用,基本算的上是千锤百炼了,比如美团的walle

jcommander值得你一个star的

@Parameters(commandDescription = "args 参数")
class CommandEntity {

    @Parameter(
        names = ["-file", "-f"],
        required = true,
        converter = FileConverter::class,
        description = "生成目标文件路径"
    )
    lateinit var file: File

    @Parameter(
        names = ["-name"], required = true,
        description = "文件名"
    )
    lateinit var name: String

    @Parameter(names = ["-group", "-bundle", "-g", "-b"], description = "唯一标识符")
    var group: String? = null

}
override fun handle(args: Array<String>) {
 val commandEntity = CommandEntity()
 JCommander.newBuilder().addObject(commandEntity).build().parse(*args)
}

实例demo如上,我也是参考了官方demo写的。通过JCommander将args解析成对应的数据实体结构。

Main 函数声明

我们要在build.gradle内的jar的task中,声明当前jar的main函数,作为命令行工具的入口。否则打出来的jar包就会报没有main函数的异常。

jar {
    exclude("**/module-info.class")
    /* from {
         configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
     }*/
    manifest {
        attributes 'Main-Class': 'com.kronos.mebium.Next'
    }
}

其中from的含义就是将一个jar包把所有的依赖都打到一起,从而形成一个fatjar,而后续因为使用了gradle提供的application插件,所以这行被我注释了。

压缩模板

我们这个脚手架最核心的就是把一部分工程模板压缩成一个zip资源文件,打包带入jar产物中。然后呢我这个人又比较懒,希望每次执行打包的时候都进行一次模板的压缩替换,所以这里我通过一部分gradle task来进行执行了。

abstract class ZipTask extends DefaultTask {
    @InputDirectory
    Provider<File> library = project.objects.property(File)

    @OutputFile
    Provider<File> outputFile = project.objects.property(File)

    @TaskAction
    def doAction() {
        def outputFile = outputFile.get()
        createFileSafety(outputFile)
        compress(library.get(), outputFile)
    }

    static File compress(final File srcDir, final File zipFile) {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))
        srcDir.eachFileRecurse({
            zos.putNextEntry(new ZipEntry(it.path - srcDir.path + (it.directory ? "/" : "")))
            if (it.file) {
                zos << it.bytes
            }
            zos.closeEntry()
        })
        zos.close()
        return zipFile
    }

    private static File createFileSafety(File file) {
        if (file.exists()) {
            file.delete()
        }
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs()
        }
        return file
    }
}

首先定义出一个task,然后定义好输入输出,输入的是一个文件夹,输出的则是一个zip的压缩文件,输入输出的地址由外部来声明。

def moduleTask = project.tasks.register("zipAndroidLib", ZipTask.class) {
    it.library.set(file("../library"))
    it.outputFile.set(file("./src/main/resources/zip/android/android.zip"))
}

def projectTask = project.tasks.register("zipAndroidProject", ZipTask.class) {
    it.library.set(file("../project"))
    it.outputFile.set(file("./src/main/resources/zip/android/project.zip"))
}

afterEvaluate {
    project.tasks.findByName("compileJava").dependsOn(moduleTask)
    project.tasks.findByName("compileJava").dependsOn(projectTask)
}

然后直接声明处两个task,之后把compileJava依赖到这两个task上去,这样就可以保证每次compileJava,这两个task都会被执行到了。编译缓存我就不说了,大家自行领悟吧。

java resource 读取方式 javaClass.classLoader.getResourceAsStream(name) 就可以了。

放飞自我

接下来我们就可以在命令行工具内放飞自我,开始很简单的通过unittest来进行代码的编写和调试了。

我们就可以通过自己熟悉的kotlin或者java来编写一个简单的cli工具,从而来进一步的做到基于工程定制化的一些方便的脚手架工具了。

生成最终产物

这里我们使用了 gradle提供的application plugin,这个插件可以将java jar包装成一个可执行文件的zip的压缩包。格式如下图所示:

用kotlin来开发一个cli工具 | 没用的技能+1_kotlin

而这个的生成指令就是,通过./gradlew impact:assembleDist 任务生成对应的二进制压缩包。

这样的好处就是我们可以省略掉java -jar xxxxx.jar的繁琐操作,通过可执行文件直接达到我们写一个cli的便利。

结尾

工程内的代码还是比较简单的,有兴趣的就自己读一下,只是一个demo而已。

还是那句因为菜,不想去学一门新语言。如果万一哪怕我的py在强那么一点点,我也考虑用py来写了,哈哈哈哈哈。

作者:究极逮虾户

标签:cli,kotlin,jar,project,File,file,没用,脚手架,main
From: https://blog.51cto.com/u_16163480/6511320

相关文章

  • Android - 无法使用任何临时 SqlClient 版本(v2.1.4、v4.1.0、v5Preview)连接到 SQL Ser
    Aconnectionwassuccessfullyestablishedwiththeserver,butthenanerroroccurredduringthepre-loginhandshake.设法用证书和IP地址解决它。使用powershell为您的IP地址创建证书:New-SelfSignedCertificate-certstorelocationcert:\localmachine\my-dns......
  • 基于Eclipse+MySQL+J2EE开发的天猫商城
    基于Eclipse+MySQL+J2EE开发的天猫商城项目介绍......
  • 30 IIC(八)iic client
    源码1.iicclient创建方法1.1通过设备树直接创建只需要在对应i2c总线下指定设备信息即可示例:需要注意这里i2c1就是I2CBUS01.2通过用户空间直接去生成i2cclient创建i2cclientechonameaddr>/sys/bus/i2c/devices/i2c-n/new_devicei2c-n:i2cadapter删除i2cc......
  • OPC DA的Client对象模型
    OPCDA的Client对象模型可以如下图表示一个OPCServer对象可以包含一个OPCGroups对象一个OPCGroups对象可以包含多个OPCGroup对象一个OPCGroup对象可以包含一个OPCItems对象一个OPCItems对象可以包含多个OPCItem对象一个OPCItem对象就是OPCServer端的一个变量以下......
  • MouseClicker v7.0
    MouseClickerv8.0BetaByHaozexuAt2022/8/1220:16原创程序,转载请注明出处。LICENSE:GPL3.0MouseClickerv8.0Copyright(C)2022haozexu项目地址更新日志UpdateLog修复mode3添加无限连点添加区间连点模式增强用户体验测试:长按模式专用更新器您也可......
  • JDK17与Hbase client的兼容性问题
    最近有1个项目升级到JDK17,里面用到了hbase-client(版本:以1.2.0-cdh5.7.1为基础,公司的大数据同学内部做了一些二次开发),启动时发现一直连不上集群,直接报错了,上hbase官网看了下:别说JDK17了,连JDK11都支持不完善,难道把JDK版本又降回去?有点不甘心,又搜索了一些资料,找到了几篇文档:htt......
  • CF521E Cycling City 解题报告
    题面一道难得恰到好处的构造题。分析因为要构造三条从\(s\)到\(t\)的路径,且三条路径中任意两条路径经过的点集的交集等于\(\{s,t\}\)。我们知道当两条路径经过的点集的交集等于\(\{s,t\}\)时,这两条路径将会构成一个环。因此题意转化为要求我们找到两个经过的边集有重合......
  • Docker CLI docker history 常用命令
    Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化。Docker是内核虚拟化,不使用Hypervisor是不完全虚拟化,依赖内核的特性实现资源隔离。本文主要介绍DockerCLI中d......
  • Kotlin协程-从理论到实战
    上一篇文章从理论上对Kotlin协程进行了部分说明,本文将在上一篇的基础上,从实战出发,继续协程之旅。从源头说起在Kotlin中,要想使用协程,首先需要使用协程创建器创建,但还有个前提——协程作用域(CoroutineScope)。在早期的Kotlin实现中,协程创建器是一等函数,也就是说我们随时随地可......
  • Kotlin协程-从一到多
    上一篇文章,我介绍了Kotlin协程的创建,使用,协作等内容。本篇将引入更多的使用场景,继续带你走进协程世界。使用协程处理异步数据流常用编程语言都会内置对同一类型不同对象的数据集表示,我们通常称之为容器类。不同的容器类适用于不同的使用场景。Kotlin的Flow就是在异步计算的需......