view src/name/blackcap/osdep/Osdep.kt @ 0:895aa9a8d628

Initial commit.
author David Barts <n5jrn@me.com>
date Sat, 08 Feb 2020 08:46:17 -0800
parents
children 5ea54efa8e45
line wrap: on
line source

/*
 * Recursively examines the specified directories for files that end in
 * .<type>.osdep, where <type> is one of unix, mac, windows, or default.
 * The file that matches the current OS type (or the type specified with
 * the -t option)
 */
package name.blackcap.osdep

import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import kotlin.collections.HashMap
import kotlin.collections.HashSet
import org.apache.commons.cli.CommandLine
import org.apache.commons.cli.DefaultParser
import org.apache.commons.cli.HelpFormatter
import org.apache.commons.cli.Option
import org.apache.commons.cli.Options
import org.apache.commons.cli.ParseException

var ostype = run {
    val rawType = System.getProperty("os.name")?.toLowerCase()
    if (rawType == null) {
        "default"
    } else if (rawType.contains("win")) {
        "windows"
    } else if (rawType.contains("mac")) {
        "mac"
    } else if (rawType.contains("nix") || rawType.contains("nux") || rawType.contains("aix") || rawType.contains("sunos")) {
        "unix"
    } else {
        "default"
    }
}

val MYNAME = "osdep"
val MYEXT = ".osdep"

val files = HashMap<String, HashSet<String>>()

var errors = 0

fun main(args: Array<String>) {
    val options = Options().apply {
        addOption("?", "help", false, "Print this help message.")
        addOption("c", "clean", false, "Clean instead of copying.")
        addOption("t", "type", true, "Specify OS type (default ${ostype}).")
    }
    var cmdLine: CommandLine? = null
    try {
        cmdLine = DefaultParser().parse(options, args)
    } catch (e: ParseException) {
        System.err.format("%s: %s%n", MYNAME, e.message)
        System.exit(2)
    }

    if (cmdLine!!.hasOption("help")) {
        val usage = MYNAME + " [--help] [--clean] [--type=type] directory [...]"
        HelpFormatter().printHelp(usage, options, false);
        System.exit(0);
    }

    val clOstype = cmdLine!!.getOptionValue("type")
    if (clOstype != null) {
        ostype = clOstype
    }

    for (arg in cmdLine.args) {
        try {
            File(arg).walk().forEach { build(it) }
        } catch (e: Exception) {
            handle(e, "cannot walk '${arg}'")
        }
    }

    val process = if (cmdLine!!.hasOption("clean")) ::clean else ::copy
    files.forEach {
        process(it.key, it.value)
    }
    System.exit(if (errors == 0) 0 else 1)
}

fun build(file: File) {
    if (!file.isFile()) {
        return
    }
    val path = file.toString()
    if (!path.endsWith(MYEXT)) {
        return
    }
    val dot2 = path.lastIndexOf('.')
    if (dot2 <= 0) {
        return
    }
    val dot1 = path.lastIndexOf('.', dot2 - 1)
    if (dot1 <= 0) {
        return
    }
    val base = path.substring(0 .. (dot1 - 1))
    val type = path.substring((dot1 + 1) .. (dot2 - 1))
    println("base=${base}, type=${type}") /* debug */
    val fset = files[base]
    if (fset != null) {
        fset.add(type)
    } else {
        files[base] = HashSet<String>().apply { add(type) }
    }
}

fun copy(base: String, types: HashSet<String>) {
    val type = if (ostype in types) ostype else "default"
    if (type !in types) {
        System.err.format("%s: type '%s' missing for '%s'%n", MYNAME, type, base)
        errors += 1
        return
    }
    copf("${base}.${type}${MYEXT}", base)
}

fun copf(source: String, target: String) {
    val sf = File(source)
    val tf = File(target)
    try {
        FileInputStream(sf).use { sfp ->
            FileOutputStream(tf).use { tfp ->
                sfp.copyTo(tfp)
            }
        }
    } catch (e: Exception) {
        handle(e, "cannot copy '${source}' to '${target}'")
        return
    }
    try {
        tf.setLastModified(sf.lastModified())
    } catch (e: Exception) {
        handle(e, "cannot set time of '${target}'")
    }
}

fun clean(base: String, @Suppress("UNUSED_PARAMETER") ignored: HashSet<String>) {
    val file = File(base)
    if (file.exists()) {
        try {
            if (!file.delete()) {
                System.err.format("%s: '%s' not deleted%n", MYNAME, base)
                errors += 1
            }
        } catch (e: Exception) {
            handle(e, "cannot delete '${base}'")
        }
    }
}

fun handle(exception: Exception, message: String) {
    val fallback = when (exception) {
        is SecurityException -> "security violation"
        is IOException -> "I/O error"
        else -> throw exception
    }
    System.err.format("%s: %s%n", MYNAME, exception.message ?: fallback)
    System.err.format("%s: %s%n", MYNAME, message)
    errors += 1
}