Program.kt

package com.hexagontk.shell

import com.hexagontk.shell.Property.Companion.HELP
import com.hexagontk.shell.Property.Companion.VERSION
import com.hexagontk.shell.formatter.DefaultFormatter
import com.hexagontk.core.CodedException
import com.hexagontk.helpers.requireNotBlank
import java.io.BufferedReader

class Program(
    val version: String? = null,
    val command: Command,
    val formatter: Formatter = DefaultFormatter(),
    val systemSetting: Boolean = false,
    val defaultCommand: List<String> = emptyList(),
) {
    constructor(
        name: String,
        version: String? = null,
        title: String? = null,
        description: String? = null,
        properties: Set<Property<*>> = emptySet(),
    ) : this(version, Command(name, title, description, properties))

    constructor(
        name: String,
        version: String? = null,
        title: String? = null,
        description: String? = null,
        properties: Set<Property<*>> = emptySet(),
        commands: Set<Command>,
    ) : this(version, Command(name, title, description, properties, commands))

    init {
        requireNotBlank(Program::version)
    }

    fun input(): String? =
        BufferedReader(System.`in`.reader()).use {
            if (it.ready()) it.readText()
            else null
        }

    fun parse(args: Array<out String>): Command =
        parse(args.toList())

    fun parse(args: Iterable<String>): Command =
        process(args)

    internal fun process(args: Iterable<String>): Command {
        val programCommand = command.findCommand(args)
        val subcommands = programCommand.name.split(" ")
        val properties = subcommands.fold(args.toList()) { a, b -> a - b }

        if (programCommand.contains(VERSION, properties))
            throw CodedException(0, formatter.summary(this, programCommand))

        if (programCommand.contains(HELP, properties))
            throw CodedException(0, formatter.help(this, programCommand))

        if (command.subcommands.isNotEmpty() && programCommand == command)
            throw CodedException(400, formatter.help(this, programCommand))

        val parsedCommand = try {
            programCommand.parse(properties)
        }
        catch (e: Exception) {
            val message = formatter.error(this, programCommand, e)
            throw CodedException(400, message, e)
        }

        return parsedCommand
    }
}