kotlin開發(fā)cli工具小技巧詳解
腳手架
腳手架是為了保證各施工過程順利進行而搭設(shè)的工作平臺
而在程序開發(fā)過程中,每個工程或者說公司也都需要一個腳手架工具。通過腳手架命令行的形式簡化開發(fā)流程,避免發(fā)生一些人為的相對低級的問題,所以這個也就是為什么叫做腳手架的原因吧。
而由于每個公司的代碼規(guī)范都不同,一般情況下會主動讓開發(fā)同學進行工程方面的cv操作,就是成本高并且容易出錯。這也就是為什么我們打算寫一些這樣的工具的原因。
在一般情況下,更多的程序猿會選擇用python去寫,因為腳本語言的靈活性,但是對于一個辣雞安卓來說會增加額外的學習成本,所以這就取決于有沒有天賦了,能不能對一門陌生的語言快速上手了。
這次文章會介紹的是用kotlin去構(gòu)建一個二進制文件,通過這個來完成腳手架cli工具的建設(shè)。
開搞
demo 工程地址 TheNext
一開始的啟發(fā)在于有時候使用一些第三方工具的時候會提供一個jar包,然后只要輸入java -jar xxx.jar就可以使用這個jar包中的Main函數(shù)了。
因為是一個jar包,所以里面的內(nèi)容肯定也都是用jvm內(nèi)的幾種語言來進行編寫的,那么這就讓我們這種老年選手看到了一絲絲的希望。
開發(fā)調(diào)試
先建立了一個java工程,然后構(gòu)建了一個main函數(shù),之后開始進行代碼編寫。但是如果每次都需要先打包之后在通過java -jar來執(zhí)行的話非常不便利開發(fā)并且debug。而且模擬入?yún)⒁不页5膼盒?,你也知道的程序猿都是懶人嗎?/p>
所以我們就借用了unittest的能力,對于入?yún)⑦M行mock進行簡單的調(diào)試功能了。
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函數(shù)通過unittest來進行模擬,這樣就可以方便我們在開發(fā)階段快速調(diào)試腳手架的能力了。
每個方法塊都可以認為是一個運行的入口,通過這個來模擬出程序所需要的入?yún)?。從而一邊完成了測試代碼的編寫,一邊完成了調(diào)試入口。
jcommander
這是一個讓我們可以更像模像樣的寫一個cli的入?yún)⒔馕龉ぞ?,即使參?shù)順序是錯亂的,我們?nèi)匀荒芙馕龀鑫覀兿胍臄?shù)據(jù)結(jié)構(gòu),讓我們的工程看起來更正規(guī)一點。而且這個庫也被很多開源項目所使用,基本算的上是千錘百煉了,比如美團的walle。
jcommander值得你一個star的
@Parameters(commandDescription = "args 參數(shù)")
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解析成對應(yīng)的數(shù)據(jù)實體結(jié)構(gòu)。
Main 函數(shù)聲明
我們要在build.gradle內(nèi)的jar的task中,聲明當前jar的main函數(shù),作為命令行工具的入口。否則打出來的jar包就會報沒有main函數(shù)的異常。
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,而后續(xù)因為使用了gradle提供的application插件,所以這行被我注釋了。
壓縮模板
我們這個腳手架最核心的就是把一部分工程模板壓縮成一個zip資源文件,打包帶入jar產(chǎn)物中。然后呢我這個人又比較懶,希望每次執(zhí)行打包的時候都進行一次模板的壓縮替換,所以這里我通過一部分gradle task來進行執(zhí)行了。
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都會被執(zhí)行到了。編譯緩存我就不說了,大家自行領(lǐng)悟吧。
java resource 讀取方式 javaClass.classLoader.getResourceAsStream(name) 就可以了。
放飛自我
接下來我們就可以在命令行工具內(nèi)放飛自我,開始很簡單的通過unittest來進行代碼的編寫和調(diào)試了。
我們就可以通過自己熟悉的kotlin或者java來編寫一個簡單的cli工具,從而來進一步的做到基于工程定制化的一些方便的腳手架工具了。
生成最終產(chǎn)物
這里我們使用了 gradle提供的application plugin,這個插件可以將java jar包裝成一個可執(zhí)行文件的zip的壓縮包。格式如下圖所示:

而這個的生成指令就是,通過./gradlew impact:assembleDist 任務(wù)生成對應(yīng)的二進制壓縮包。
這樣的好處就是我們可以省略掉java -jar xxxxx.jar的繁瑣操作,通過可執(zhí)行文件直接達到我們寫一個cli的便利。
結(jié)尾
工程內(nèi)的代碼還是比較簡單的,有興趣的就自己讀一下,只是一個demo而已。
還是那句因為菜,不想去學一門新語言。如果萬一哪怕我的py在強那么一點點,我也考慮用py來寫了,哈哈哈哈哈。
以上就是kotlin開發(fā)cli工具小技巧詳解的詳細內(nèi)容,更多關(guān)于kotlin開發(fā)cli工具的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android Studio安裝配置、環(huán)境搭建詳細步驟及基本使用的詳細教程
這篇文章主要介紹了Android Studio安裝配置、環(huán)境搭建詳細步驟及基本使用的詳細教程,需要的朋友可以參考下2020-03-03
android中實現(xiàn)在ImageView上隨意畫線涂鴉的方法
今天小編就為大家分享一篇android中實現(xiàn)在ImageView上隨意畫線涂鴉的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-10-10

