feat: added core logic
This commit is contained in:
@@ -9,6 +9,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.kargs:kargs:1.0.8")
|
implementation("org.kargs:kargs:1.0.8")
|
||||||
|
implementation("com.varabyte.kotter:kotter-jvm:1.2.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ fun main(args: Array<String>) {
|
|||||||
val parser = Parser("pledge")
|
val parser = Parser("pledge")
|
||||||
|
|
||||||
parser.subcommands(
|
parser.subcommands(
|
||||||
CheckCommand()
|
LintCommand()
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package io.pledge.commands
|
|
||||||
|
|
||||||
import org.kargs.*
|
|
||||||
|
|
||||||
class CheckCommand : Subcommand("check", "Checks if commit title is valid") {
|
|
||||||
override fun execute() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
src/main/kotlin/io/pledge/commands/LintCommand.kt
Normal file
11
src/main/kotlin/io/pledge/commands/LintCommand.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package io.pledge.commands
|
||||||
|
|
||||||
|
import org.kargs.*
|
||||||
|
|
||||||
|
class LintCommand : Subcommand("lint", "Checks if commit title is valid") {
|
||||||
|
val message by Option(ArgType.String, "message", "m", description = "Message to lint", required = true)
|
||||||
|
|
||||||
|
override fun execute() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/main/kotlin/io/pledge/core/App.kt
Normal file
82
src/main/kotlin/io/pledge/core/App.kt
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package io.pledge.core
|
||||||
|
|
||||||
|
import io.pledge.core.*
|
||||||
|
|
||||||
|
class App {
|
||||||
|
private var type: CommitType = CommitType.EMPTY
|
||||||
|
private var scope: String? = null
|
||||||
|
private var description: String = ""
|
||||||
|
private val body: MutableList<String> = mutableListOf()
|
||||||
|
private val changes: MutableList<Change> = mutableListOf()
|
||||||
|
|
||||||
|
/* Sets the type of the commit */
|
||||||
|
fun setType(type: CommitType): Result<Unit> {
|
||||||
|
if (type == CommitType.EMPTY) return Result.failure(Exception("Commit type cannot be EMPTY"))
|
||||||
|
|
||||||
|
this.type = type
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets the scope of the commit */
|
||||||
|
fun setScope(value: String): Result<Unit> {
|
||||||
|
val validScopePattern = Regex("^[a-z0-9-]+$") // lowercase/hyphens/numbers only
|
||||||
|
if (!validScopePattern.matches(value)) return Result.failure(Exception("Scope can only be lowercase/hyphens/numbers"))
|
||||||
|
if (value.length > 20) return Result.failure(Exception("Scope cannot be longer than 20 characters"))
|
||||||
|
|
||||||
|
this.scope = value
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets the description of the commit */
|
||||||
|
fun setDescription(value: String): Result<Unit> {
|
||||||
|
if (value.isBlank()) return Result.failure(Exception("Description cannot be empty"))
|
||||||
|
if (!value.first().isLetter() || !value.first().isLowerCase()) return Result.failure(Exception("Description must start with lowercase letter"))
|
||||||
|
if (value.endsWith(".")) return Result.failure(Exception("Description cannot end with a period"))
|
||||||
|
if (value.length > 50) return Result.failure(Exception("Description cannot be longer than 50 characters"))
|
||||||
|
|
||||||
|
this.description = value
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets the body of the commit */
|
||||||
|
fun setBody(body: List<String>): Result<Unit> {
|
||||||
|
this.body.clear()
|
||||||
|
this.body.addAll(body)
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a single body line */
|
||||||
|
fun addBodyLine(line: String): Result<Unit> {
|
||||||
|
if (line.length > 72) return Result.failure(Exception("Body line cannot be longer than 72 characters"))
|
||||||
|
this.body.add(line)
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets the changes in the commit */
|
||||||
|
fun setChanges(changes: List<Change>): Result<Unit> {
|
||||||
|
this.changes.clear()
|
||||||
|
this.changes.addAll(changes)
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a single change */
|
||||||
|
fun addChange(change: Change): Result<Unit> {
|
||||||
|
this.changes.add(change)
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns current state for preview */
|
||||||
|
fun getCurrentState(): Commit = Commit(type, scope, description, body.toList(), changes.toList())
|
||||||
|
|
||||||
|
/* Retuns immutable commit */
|
||||||
|
fun generateCommit(): Commit = Commit(type, scope, description, body.toList(), changes.toList())
|
||||||
|
|
||||||
|
/* Reset the app state */
|
||||||
|
fun reset() {
|
||||||
|
type = CommitType.EMPTY
|
||||||
|
scope = null
|
||||||
|
description = ""
|
||||||
|
body.clear()
|
||||||
|
changes.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/main/kotlin/io/pledge/core/Commit.kt
Normal file
12
src/main/kotlin/io/pledge/core/Commit.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package io.pledge.core
|
||||||
|
|
||||||
|
import io.pledge.core.CommitType
|
||||||
|
import io.pledge.core.Change
|
||||||
|
|
||||||
|
data class Commit(
|
||||||
|
val type: CommitType,
|
||||||
|
val scope: String? = null,
|
||||||
|
val description: String,
|
||||||
|
val body: List<String> = emptyList(),
|
||||||
|
val changes: List<Change> = emptyList(),
|
||||||
|
)
|
||||||
15
src/main/kotlin/io/pledge/core/CommitTypes.kt
Normal file
15
src/main/kotlin/io/pledge/core/CommitTypes.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package io.pledge.core
|
||||||
|
|
||||||
|
enum class CommitType(val value: String, val desc: String) {
|
||||||
|
FEAT("feat", "A new feature"),
|
||||||
|
FIX("fix", "A bug fix"),
|
||||||
|
REFACTOR("refactor", "Code changes that neither fixes a bug nor adds a feature"),
|
||||||
|
DOCS("docs", "Documentation only changes"),
|
||||||
|
STYLE("style", "Changes that do not affect the meaning of the code"),
|
||||||
|
TEST("test", "Add missing tests or correcting existing tests"),
|
||||||
|
CHORE("chore", "Changes to the build process or auxilliary tools"),
|
||||||
|
REVERT("revert", "Reverts to a previous commit"),
|
||||||
|
MERGE("merge", "Merging branches"),
|
||||||
|
DEPLOY("deploy", "Deployment related changes"),
|
||||||
|
EMPTY("", ""),
|
||||||
|
}
|
||||||
145
src/main/kotlin/io/pledge/core/Converter.kt
Normal file
145
src/main/kotlin/io/pledge/core/Converter.kt
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package io.pledge.core
|
||||||
|
|
||||||
|
import io.pledge.core.Commit
|
||||||
|
import io.pledge.core.CommitType
|
||||||
|
import io.pledge.core.ChangeType
|
||||||
|
|
||||||
|
object Converter {
|
||||||
|
/* Converts Commit object into a git commit */
|
||||||
|
fun convertToGitCommit(commit: Commit): String {
|
||||||
|
val typeString = commit.type.toString().lowercase()
|
||||||
|
|
||||||
|
// Build the commit header: type(scope): description
|
||||||
|
val header = buildString {
|
||||||
|
append(typeString)
|
||||||
|
commit.scope?.let { scope -> append("($scope)") }
|
||||||
|
append(": ")
|
||||||
|
append(commit.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the full commit message
|
||||||
|
return buildString {
|
||||||
|
append(header)
|
||||||
|
|
||||||
|
// Add body if present
|
||||||
|
if (commit.body.isNotEmpty()) {
|
||||||
|
append("\n\n")
|
||||||
|
append(commit.body.joinToString("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add file changes if present
|
||||||
|
if (commit.changes.isNotEmpty()) {
|
||||||
|
append("\n\n")
|
||||||
|
commit.changes.forEach { change ->
|
||||||
|
val changeSymbol = when (change.change) {
|
||||||
|
ChangeType.ADDED -> "+"
|
||||||
|
ChangeType.MODIFIED -> "~"
|
||||||
|
ChangeType.DELETED -> "-"
|
||||||
|
}
|
||||||
|
append("$changeSymbol ${change.file}")
|
||||||
|
if (change != commit.changes.last()) {
|
||||||
|
append("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts git commit into Commit object */
|
||||||
|
fun convertFromGitCommit(commitMessage: String): Commit {
|
||||||
|
val lines = commitMessage.split("\n")
|
||||||
|
if (lines.isEmpty()) return Commit(CommitType.EMPTY, null, "", emptyList(), emptyList())
|
||||||
|
|
||||||
|
// Parse the header line: type(scope): description
|
||||||
|
val headerLine = lines[0]
|
||||||
|
val (type, scope, description) = parseHeader(headerLine)
|
||||||
|
|
||||||
|
// Find where the body starts and ends, and where the file changes start
|
||||||
|
var bodyStartIndex = -1
|
||||||
|
var bodyEndIndex = -1
|
||||||
|
var changesStartIndex = -1
|
||||||
|
var changesEndIndex = -1
|
||||||
|
|
||||||
|
for (i in 1 until lines.size) {
|
||||||
|
val line = lines[i]
|
||||||
|
when {
|
||||||
|
line.isEmpty() && bodyStartIndex == -1 -> {
|
||||||
|
// First empty line after header - body might start next
|
||||||
|
if (i + 1 < lines.size && lines[i + 1].isNotEmpty() && !isFileChangeLine(lines[i + 1])) {
|
||||||
|
bodyStartIndex = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line.isEmpty() && bodyStartIndex != -1 && bodyEndIndex == -1 -> {
|
||||||
|
// Empty line after body content - body ends here
|
||||||
|
bodyEndIndex = i - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
isFileChangeLine(line) && changesStartIndex == -1 -> {
|
||||||
|
// First file change line
|
||||||
|
changesStartIndex = i
|
||||||
|
if (bodyStartIndex != -1 && bodyEndIndex == -1) {
|
||||||
|
bodyEndIndex = i - 2 // Account for empty line before changes
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract body
|
||||||
|
val body = if (bodyStartIndex != -1 && bodyEndIndex != -1 && bodyEndIndex >= bodyStartIndex) {
|
||||||
|
lines.subList(bodyStartIndex, bodyEndIndex + 1).filter { it.isNotEmpty() }
|
||||||
|
} else if (bodyStartIndex != -1 && changesStartIndex == -1) {
|
||||||
|
// Body goes to end of commit if no file changes
|
||||||
|
lines.subList(bodyStartIndex, lines.size).filter { it.isNotEmpty() }
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract file changes
|
||||||
|
val changes = if (changesStartIndex != -1) {
|
||||||
|
lines.subList(changesStartIndex, lines.size)
|
||||||
|
.filter { it.isNotEmpty() && isFileChangeLine(it) }
|
||||||
|
.mapNotNull { parseFileChange(it) }
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Commit(type, scope, description, body, changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseHeader(headerLine: String): Triple<CommitType, String?, String> {
|
||||||
|
// Regex to match: type(scope): description or type: description
|
||||||
|
val regex = Regex("""^(\w+)(?:\(([^)]+)\))?\s*:\s*(.+)$""")
|
||||||
|
val matchResult = regex.find(headerLine)
|
||||||
|
|
||||||
|
return if (matchResult != null) {
|
||||||
|
val typeString = matchResult.groupValues[1].uppercase()
|
||||||
|
val scope = matchResult.groupValues[2].takeIf { it.isNotEmpty() }
|
||||||
|
val description = matchResult.groupValues[3]
|
||||||
|
|
||||||
|
val commitType = try {
|
||||||
|
CommitType.valueOf(typeString)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
CommitType.EMPTY // Fallback for unknown types
|
||||||
|
}
|
||||||
|
|
||||||
|
Triple(commitType, scope, description)
|
||||||
|
} else {
|
||||||
|
// Fallback if header doesn't match expected format
|
||||||
|
Triple(CommitType.EMPTY, null, headerLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isFileChangeLine(line: String): Boolean = line.startsWith("+ ") || line.startsWith("~ ") || line.startsWith("- ")
|
||||||
|
|
||||||
|
private fun parseFileChange(line: String): Change? {
|
||||||
|
return when {
|
||||||
|
line.startsWith("+ ") -> Change(line.substring(2), ChangeType.ADDED)
|
||||||
|
line.startsWith("~ ") -> Change(line.substring(2), ChangeType.MODIFIED)
|
||||||
|
line.startsWith("- ") -> Change(line.substring(2), ChangeType.DELETED)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/main/kotlin/io/pledge/core/TrackedFiles.kt
Normal file
53
src/main/kotlin/io/pledge/core/TrackedFiles.kt
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package io.pledge.core
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
|
||||||
|
fun getTrackedFiles(repoPath: String): List<Change> {
|
||||||
|
val changes = mutableListOf<Change>()
|
||||||
|
|
||||||
|
// Get staged changes
|
||||||
|
val stagedProcess = ProcessBuilder("git", "status", "--porcelain")
|
||||||
|
.directory(File(repoPath))
|
||||||
|
.start()
|
||||||
|
|
||||||
|
stagedProcess.inputStream.bufferedReader().useLines { lines ->
|
||||||
|
lines.forEach { line ->
|
||||||
|
if (line.length >= 3) {
|
||||||
|
val gitStatus = line.take(2)
|
||||||
|
val filePath = line.substring(3)
|
||||||
|
|
||||||
|
val changeType = mapGitStatusToChangeType(gitStatus)
|
||||||
|
changeType?.let {
|
||||||
|
changes.add(Change(filePath, it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapGitStatusToChangeType(gitStatus: String): ChangeType? {
|
||||||
|
return when {
|
||||||
|
"A" in gitStatus -> ChangeType.ADDED
|
||||||
|
"D" in gitStatus -> ChangeType.DELETED
|
||||||
|
"M" in gitStatus -> ChangeType.MODIFIED
|
||||||
|
"R" in gitStatus -> ChangeType.MODIFIED // Renamed files are considered changed
|
||||||
|
"C" in gitStatus -> ChangeType.MODIFIED // Copied files are considered changed
|
||||||
|
"U" in gitStatus -> ChangeType.MODIFIED // Unmerged files are considered modified
|
||||||
|
else -> null // Ignore untracked (??) and other statuses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Change(
|
||||||
|
val file: String,
|
||||||
|
val change: ChangeType
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class ChangeType {
|
||||||
|
ADDED,
|
||||||
|
DELETED,
|
||||||
|
MODIFIED,
|
||||||
|
}
|
||||||
170
src/main/kotlin/io/pledge/core/Validator.kt
Normal file
170
src/main/kotlin/io/pledge/core/Validator.kt
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package io.pledge.core
|
||||||
|
|
||||||
|
import io.pledge.core.*
|
||||||
|
|
||||||
|
object Validator {
|
||||||
|
|
||||||
|
fun validateCommit(commit: Commit): ValidationResult {
|
||||||
|
val errors = mutableListOf<String>()
|
||||||
|
|
||||||
|
// Validate commit type
|
||||||
|
if (commit.type == CommitType.EMPTY) {
|
||||||
|
errors.add("Commit type cannot be EMPTY")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate description
|
||||||
|
validateDescription(commit.description)?.let { error ->
|
||||||
|
errors.add(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate scope if present
|
||||||
|
commit.scope?.let { scope ->
|
||||||
|
validateScope(scope)?.let { error ->
|
||||||
|
errors.add(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate body lines
|
||||||
|
commit.body.forEachIndexed { index, line ->
|
||||||
|
validateBodyLine(line, index)?.let { error ->
|
||||||
|
errors.add(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate changes
|
||||||
|
commit.changes.forEach { change ->
|
||||||
|
validateChange(change)?.let { error ->
|
||||||
|
errors.add(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult(errors.isEmpty(), errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateDescription(description: String): String? {
|
||||||
|
return when {
|
||||||
|
description.isBlank() -> "Description cannot be empty"
|
||||||
|
description.length > 72 -> "Description should not exceed 72 characters (current: ${description.length})"
|
||||||
|
description.first().isUpperCase() -> "Description should start with lowercase letter"
|
||||||
|
description.endsWith(".") -> "Description should not end with a period"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateScope(scope: String): String? {
|
||||||
|
return when {
|
||||||
|
scope.isBlank() -> "Scope cannot be empty if provided"
|
||||||
|
scope.length > 20 -> "Scope should not exceed 20 characters (current: ${scope.length})"
|
||||||
|
!scope.matches(Regex("^[a-z0-9-]+$")) -> "Scope should only contain lowercase letters, numbers, and hyphens"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateBodyLine(line: String, index: Int): String? {
|
||||||
|
return when {
|
||||||
|
line.length > 72 -> "Body line ${index + 1} should not exceed 72 characters (current: ${line.length})"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateChange(change: Change): String? {
|
||||||
|
return when {
|
||||||
|
change.file.isBlank() -> "File path cannot be empty"
|
||||||
|
change.file.contains("..") -> "File path should not contain '..' (security risk)"
|
||||||
|
change.file.startsWith("/") -> "File path should be relative, not absolute"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional validation methods for specific scenarios
|
||||||
|
|
||||||
|
fun validateCommitType(type: String): ValidationResult {
|
||||||
|
val errors = mutableListOf<String>()
|
||||||
|
|
||||||
|
if (type.isBlank()) {
|
||||||
|
errors.add("Commit type cannot be empty")
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
val commitType = CommitType.valueOf(type.uppercase())
|
||||||
|
if (commitType == CommitType.EMPTY) {
|
||||||
|
errors.add("Commit type cannot be EMPTY")
|
||||||
|
}
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
errors.add("Invalid commit type: '$type'. Valid types are: ${CommitType.values().filter { it != CommitType.EMPTY }.joinToString(", ")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult(errors.isEmpty(), errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateGitCommitMessage(commitMessage: String): ValidationResult {
|
||||||
|
val errors = mutableListOf<String>()
|
||||||
|
|
||||||
|
if (commitMessage.isBlank()) {
|
||||||
|
errors.add("Commit message cannot be empty")
|
||||||
|
return ValidationResult(false, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
val lines = commitMessage.split("\n")
|
||||||
|
val headerLine = lines[0]
|
||||||
|
|
||||||
|
// Validate header format
|
||||||
|
val headerRegex = Regex("""^(\w+)(?:\(([^)]+)\))?\s*:\s*(.+)$""")
|
||||||
|
if (!headerRegex.matches(headerLine)) {
|
||||||
|
errors.add("Header line must follow format: 'type(scope): description' or 'type: description'")
|
||||||
|
} else {
|
||||||
|
val matchResult = headerRegex.find(headerLine)!!
|
||||||
|
val type = matchResult.groupValues[1]
|
||||||
|
val scope = matchResult.groupValues[2].takeIf { it.isNotEmpty() }
|
||||||
|
val description = matchResult.groupValues[3]
|
||||||
|
|
||||||
|
// Validate each component
|
||||||
|
validateCommitType(type).errors.forEach { errors.add(it) }
|
||||||
|
scope?.let { validateScope(it)?.let { error -> errors.add(error) } }
|
||||||
|
validateDescription(description)?.let { error -> errors.add(error) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate total length
|
||||||
|
if (commitMessage.length > 2000) {
|
||||||
|
errors.add("Commit message is too long (${commitMessage.length} characters). Consider keeping it under 2000 characters.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult(errors.isEmpty(), errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateFileChanges(changes: List<Change>): ValidationResult {
|
||||||
|
val errors = mutableListOf<String>()
|
||||||
|
|
||||||
|
if (changes.isEmpty()) {
|
||||||
|
errors.add("At least one file change is required")
|
||||||
|
return ValidationResult(false, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate files
|
||||||
|
val duplicateFiles = changes.groupBy { it.file }
|
||||||
|
.filter { it.value.size > 1 }
|
||||||
|
.keys
|
||||||
|
|
||||||
|
if (duplicateFiles.isNotEmpty()) {
|
||||||
|
errors.add("Duplicate files found: ${duplicateFiles.joinToString(", ")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each change
|
||||||
|
changes.forEach { change ->
|
||||||
|
validateChange(change)?.let { error ->
|
||||||
|
errors.add(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult(errors.isEmpty(), errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ValidationResult(
|
||||||
|
val isValid: Boolean,
|
||||||
|
val errors: List<String> = emptyList()
|
||||||
|
) {
|
||||||
|
fun hasErrors(): Boolean = errors.isNotEmpty()
|
||||||
|
|
||||||
|
fun getErrorMessage(): String = errors.joinToString("; ")
|
||||||
|
}
|
||||||
11
src/main/kotlin/io/pledge/ui/CommitBuilder.kt
Normal file
11
src/main/kotlin/io/pledge/ui/CommitBuilder.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package io.pledge.ui
|
||||||
|
|
||||||
|
import com.varabyte.kotter.foundation.*
|
||||||
|
import com.varabyte.kotter.foundation.input.*
|
||||||
|
import com.varabyte.kotter.foundation.text.*
|
||||||
|
import com.varabyte.kotter.runtime.concurrent.*
|
||||||
|
import io.pledge.core.*
|
||||||
|
|
||||||
|
class CommitBuilder {
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user