kotlin开发cli工具小技巧详解

目录
  • 脚手架
  • 开搞
    • 开发调试
      • jcommander
    • 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的压缩包。格式如下图所示:

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

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

结尾

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

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

以上就是kotlin开发cli工具小技巧详解的详细内容,更多关于kotlin开发cli工具的资料请关注我们其它相关文章!

(0)

相关推荐

  • Kotlin协程Flow生命周期及异常处理浅析

    目录 正文 Flow基本概念 Flow生命周期 处理异常 上游或者中间异常使用catch 下游使用try-catch 切换执行线程 终止操作符 "冷的数据流"从何而来 正文 Kotlin协程中的Flow主要用于处理复杂的异步数据,以一种”流“的方式,从上到下依次处理,和RxJava的处理方式类型,但是比后者更加强大. Flow基本概念 Flow中基本上有三个概念,即 发送方,处理中间层,接收方,可以类比水利发电站中的上游,发电站,下游的概念, 数据从上游开始发送”流淌“至中间站被”处理

  • 详解Flink同步Kafka数据到ClickHouse分布式表

    目录 引言 什么是ClickHouse? 创建复制表 通过jdbc写入 引言 业务需要一种OLAP引擎,可以做到实时写入存储和查询计算功能,提供高效.稳健的实时数据服务,最终决定ClickHouse 什么是ClickHouse? ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS). 列式数据库更适合于OLAP场景(对于大多数查询而言,处理速度至少提高了100倍),下面详细解释了原因(通过图片更有利于直观理解),图片来源于ClickHouse中文官方文档. 行式 列

  • 代替Vue Cli的全新脚手架工具create vue示例解析

    目录 前言 npm init npx 源码 主流程入口 获取参数 对话选项 默认值 emptyDir函数 模板写入 简述 快照 总结 前言 美国时间 2021 年 10 月 7 日早晨,Vue 团队等主要贡献者举办了一个 Vue Contributor Days 在线会议,蒋豪群(知乎胖茶,Vue.js 官方团队成员,Vue-CLI 核心开发),在会上公开了create-vue,一个全新的脚手架工具. create-vue 使用 npm init vue 一行命令就能快速的创建基于Vite的Vu

  • Kotlin Channel处理多个数据组合的流

    目录 结论先行 Channel使用示例 Channel的源码 安全的从Channel中取数据 热的数据流从何而来 Channel能力的来源 结论先行 Kotlin协程中的Channel用于处理多个数据组合的流,随用随取,时刻准备着,就像自来水一样,打开开关就有水了. Channel使用示例 fun main() = runBlocking { logX("开始") val channel = Channel<Int> { } launch { (1..3).forEach{

  • kotlin开发cli工具小技巧详解

    目录 脚手架 开搞 开发调试 jcommander Main 函数声明 压缩模板 放飞自我 生成最终产物 结尾 脚手架 脚手架是为了保证各施工过程顺利进行而搭设的工作平台 而在程序开发过程中,每个工程或者说公司也都需要一个脚手架工具.通过脚手架命令行的形式简化开发流程,避免发生一些人为的相对低级的问题,所以这个也就是为什么叫做脚手架的原因吧. 而由于每个公司的代码规范都不同,一般情况下会主动让开发同学进行工程方面的cv操作,就是成本高并且容易出错.这也就是为什么我们打算写一些这样的工具的原因.

  • Android 开发与代码无关技巧详解

    目录 1.如何找到代码 (1)无敌搜索大法 (2)log输出大法 (3)profiler查看大法 (4)万能法找到页面 2.如何解决bug (1)先看再想最后动手 (2)改变现状 (3)是技术问题还是业务问题 (4)张张嘴远胜于动动手 (5)bug解决不了,那就解决提出bug的人 (6)解决了bug之后 3.如何实现不会的功能 (1)不要急着拒绝 (2)大事化小小事化了 心态要稳,天塌了有个高的顶着 1.如何找到代码 作为客户端的开发,工作中经常遇到,后端的同事来帮忙找接口详情.产品经理来询问之

  • Java SpringBoot开发小技巧详解

    目录 一.SpringBoot开发小技巧 1.1 Lombok 1.2 dev-tools 1.3 Spring Initializr 总结 一.SpringBoot开发小技巧 1.1 Lombok 作用:在程序编译的时候,自动帮我们生成setter和getter方法以及我们的toString方法和我们的全参和无参构造器等等. 那么,怎么用呢?很简单,用下边这四个注解就行了: 1.@Data:自动生成setter和getter方法. 2.@ToString:自动生成toString方法. 3.@

  • 在Kotlin开发中如何使用集合详解

    关于 Kotlin 开发 使用 Kotlin 开发 Android App 在 Java 工程师群体中变得越来越流行.如果你由于某些原因错过了 Kotlin,我们强烈建议你看一下这篇文章. 对于那些处在技术前沿和喜欢 Kotlin 的开发者来说,本篇文章和他们息息相关.所以,下面就让我们来看一下怎样在 Kotlin 中使用集合吧. Kotlin中的集合是基于 Java 集合的框架.本篇文章主要讲的是 kotlin.collections 包中的几个特性. 数据处理 Kotlin 中有一个拓展函数

  • js异步编程小技巧详解

    异步回调是js的一大特性,理解好用好这个特性可以写出很高质量的代码.分享一些实际用的一些异步编程技巧. 1.我们有些应用环境是需要等待两个http请求或IO操作返回后进行后续逻辑的处理.而这种情况使用回调嵌套代码会显得很难维护,而且也没有充分使用js的异步优势. 看下实例(为了大家容易理解使用了jq作为示例) $.get("获取数据1.html",function(data,status){ $.get("获取数据2.html",function(data1,sta

  • Kotlin 嵌套函数开发技巧详解

    目录 1.嵌套函数 2.@JvmOverloads快捷实现函数重载 3.延迟初始化lateinit var 4.@JvmField减少属性set和get方法的生成 1.嵌套函数 业务开发中,我们可能会遇到这样一个场景:一个函数只会被某一处多次调用,且不想让这个函数在该类的其他地方调用,这个时候就需要对这个函数的访问性进行进一步限制. private是无法满足的,这个时候我们就可以使用嵌套函数提供更好的封装: fun test1() { //被限制访问行的函数 fun test2(content:

  • Hutool开发利器MapProxy类使用技巧详解

    目录 概述 场景引入 MapProxy使用 MapProxy源码解析 总结 概述 Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”. 目前公司项目中主要采用Hutool作为项目的工具包,相对于google的guava, hutool的工具类采用中文注释,更加符合国人使用.所谓知己知彼,我们需要了解Hutool都具有什么样的功能,才能够最大化发挥它的价值. 本文主要就hutool

  • Vue安装浏览器开发工具的步骤详解

    开发vue时,浏览器有一个好的开发调试工具能让开发事半功倍,磨刀不误砍柴工. 步骤 1.下载工具 地址:  https://github.com/vuejs/vue-devtools 2.安装依赖 cmd进入vue-devtools文件夹,安装相关依赖,依次执行npm install,再执行npm run build. 3.修改配置 打开shells>chrome>src>manifest.json,修改"persistent":false为true. 4.浏览器安装

  • 原生微信小程序开发中 redux 的使用详解

    前提 复杂场景中有不少数据需要在多个不同页面间来回使用和修改.但是小程序页面直接的数据通信方式十分的简单.通常情况需要自己维护一个全局的对象来存放共有数据.但是,简单的维护一个共有数据实体,会随着业务逻辑的不断复杂化而变的过分庞大,并且数据的修改往往无法很好的溯源.加之公共数据实体中数据的修改和页面的UI之间没有太好的同步手段,往往需要在页面和对应的数据实体中同时都维护一份相同的数据,操作十分的不方便. 之前使用过Taro以react+redux的结构来开发微信小程序,依托redux整体上可以解

  • 微信小程序开发图片拖拽实例详解

    微信小程序开发图片拖拽实例详解 1.编写页面结构:moveimg.wxml <view class="container"> <view class="cnt"> <image class="image-style" src="../uploads/foods.jpg" style="left:{{ballleft}}px;width:{{screenWidth}}px" bi

随机推荐