
geet hash-object 명령어는 여러 옵션들을 받을 수 있음
원래 parseCommandLine으로 입력된 명령어들의 옵션들도 다 정리를 하려고 했음
그러나 옵션마다 다음으로 입력되는 값이 필요한 옵션도 있고 아닌 것도 있고 복잡해서 그냥 사용하던 parseCommandLine 함수를 없애고 args 자체를 넘겨주기로 수정함
// Main.kt
import geet.processGeet
fun main(commandLines: Array<String>) {
processGeet(commandLines)
}
그리고 다음 과정에서 그냥 args 자체를 읽어서 그에 따라 처리하기로 결정
processGeet도 다음과 같이 수정함
// geet/ProcessGeet.kt
package geet
import ..
fun processGeet(commandLines: Array<String>): Unit {
if (commandLines.isEmpty()) { // 입력된 값이 아무것도 없는 경우 guideGeet() 호출하고 리턴
guideGeet()
return
}
when (commandLines[0]) {
"init" -> geetInit()
"hash-object" -> geetHashObject(commandLines) // 입력된 커맨드라인들 자체를 넘겨줌
else -> throw NotSupportedCommand(commandLines[0])
}
}
hash-object 명령어를 처리하는 geetHashObject 함수는 아래와 같음
// geet/commands/plumbing/GeetHashObject.kt
package geet.commands.plumbing
import ..
data class GeetHashObjectOptions( // 사용자가 입력한 커맨드라인으로부터 받아온 옵션 정보 저장하는 데이터 클래스
var type: String = "blob", // 아무것도 입력하지 않으면 기본값 blob
var write: Boolean = false, // 아무것도 입력하지 않으면 기본값 false
var path: String = "" // 아무것도 입력하지 않으면 ""
)
fun geetHashObject(commandLines: Array<String>) {
val options = getHashObjectOptions(commandLines) // 커맨드라인으로부터 요청 옵션 가져오기
createHashObject(options) // 옵션을 토대로 개체 생성
}
fun getHashObjectOptions(commandLines: Array<String>): GeetHashObjectOptions {
val options = GeetHashObjectOptions()
var index: Int = 1
while (index < commandLines.size) {
when (commandLines[index]) {
"-t" -> {
// 만약 사용자가 요청한 개체 타입(-t 다음에 입력되어야 함)이 blob, tree, commit, tag가 아닌 경우
if (!isGeetObjectType(commandLines[index + 1])) {
println("'-t' 옵션에 대하여 올바른 개체 타입이 지정되지 않았습니다.: ${commandLines[index + 1]}")
// TODO: 에러 처리
}
options.type = commandLines[index + 1] // 개체 타입
index += 2 // 개체 타입까지 건너 뛰어 인덱스 2개 증가
}
"-w" -> {
options.write = true // 저장 옵션 설정
index += 1
}
else -> {
// 만약 이미 파일명이 지정이 되었다면
if (options.path != "") {
println("지정할 수 없는 옵션입니다.: ${commandLines[index]}")
// TODO: 에러 처리
}
options.path = commandLines[index] // 파일명 지정
index += 1
}
}
}
if (options.path == "") { // 파일명이 입력이 되지 않은 경우
println("파일 경로가 지정되지 않았습니다.")
// TODO: 에러 처리
}
return options // 옵션 반환
}
여기서 사용한 isGeetObjectType 함수나 createHashObject 함수는 다시 쓰일 가능성이 있어 GeetUtil.kt에 저장해두었다.
// geet/util/GeetUtil.kt
package geet.util
import ..
// 해시화에 쓰일 인스턴스
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-1")
// 문자열이 올바른 개체 타입인지
fun isGeetObjectType(type: String): Boolean {
val typeLowerCase = type.lowercase()
return typeLowerCase == "blob" ||
typeLowerCase == "tree" ||
typeLowerCase == "commit" ||
typeLowerCase == "tag"
}
// 개체 생성 함수
fun createHashObject(options: GeetHashObjectOptions) {
val file = File(options.path)
if (!file.exists()) { // 만약 파일이 존재하지 않다면
println("파일이 존재하지 않습니다.: ${options.path}")
// TODO: 에러 처리
}
val hashString = getHashString(options, file) // 파일 내용 및 개체 타입으로 해시(SHA-1) 생성
val directoryName = hashString.substring(0, 2) // 저장될 폴더 이름
val fileName = hashString.substring(2) // 저장될 파일 이름
val compressedContents = compressToZlib(file.readText()) // 파일 내용 zlib으로 압축
File(".geet/objects/$directoryName").mkdirs() // 폴더 생성
File(".geet/objects/$directoryName/$fileName").writeText(compressedContents) // 파일 생성
println(hashString)
println("개체가 저장되었습니다. : .geet/objects/$directoryName/$fileName")
}
// SHA-1 값을 리턴하는 함수
fun getHashString(options: GeetHashObjectOptions, file: File): String {
val content = file.readText()
val header = "${options.type} ${content.length}\u0000" // ex) "blob 12\u0000", \u0000은 널문자
val store = header + content
val hash = messageDigest.digest(store.toByteArray())
return hash.joinToString("") {
String.format("%02x", it)
}
}
// 문자열 zlib으로 압축하는 함수
fun compressToZlib(contents: String): String {
val inputData = contents.toByteArray()
val outputStream = ByteArrayOutputStream()
val deflater = Deflater()
val deflaterOutputStream = DeflaterOutputStream(outputStream, deflater)
deflaterOutputStream.write(inputData)
deflaterOutputStream.close()
// 원래 ByteArray로 압축, 압축 해제를 하지만 파일 내용으로 저장해야 하므로 String으로 변환
// 그냥 toString()을 사용하면 내용 손실의 우려가 있어 Base64 Encoder 및 Decoder를 이용하여 변환
return Base64.getEncoder().encodeToString(outputStream.toByteArray())
}
// zlib 압축 해제하는 함수
fun decompressFromZlib(zlibContents: String): String {
val decodedByteArray = Base64.getDecoder().decode(zlibContents)
val inputStream = ByteArrayInputStream(decodedByteArray)
val inflater = Inflater()
val inflaterInputStream = InflaterInputStream(inputStream, inflater)
val outputStream = ByteArrayOutputStream()
val buffer = ByteArray(1024)
var length: Int
while (inflaterInputStream.read(buffer).also { length = it } > 0) {
outputStream.write(buffer, 0, length)
}
return outputStream.toString()
}
test.txt를 저장하니 잘 되는 모습!
