Skip to content

MHmorgan/pykt

Repository files navigation

Pykt 🚀

pykt contains functionality missing from Kotlin's standard library.

Only created for the JVM target.

📋 Overview

This library provides essential utilities and functionality for JVM Kotlin development:

  • 📊 Data Formats: S-expressions, INI, CSV, TOML, and Properties support
  • 🔧 Shell Processing: Command-line lexing and argument parsing (shlex)
  • 🧮 Data Processing: Itertools, statistics, and functional utilities
  • ⏰ Time Handling: Time parsing utilities and temporal serialization
  • 💾 Performance: Memoization and caching utilities

🔧 Components

Complete S-expression parsing and manipulation library with support for configuration files.

Inspired by OCaml's Sexp from core.

Key features:

  • 🔗 Kotlin-idiomatic API with extension functions (String.parseSexp(), File.parseSexp())
  • 📞 Callback-based parsing support
  • ⚙️ Configuration file support with typed getters (SexpConfig)
  • 🛡️ Comprehensive error handling and validation

Example usage:

// Parse S-expressions
val sexp = "(config (debug true) (port 8080))".parseSexp()

// Configuration file support with typed getters
val config = SexpConfig("""
    (server
     (host "localhost")
     (port 8080)
     (debug true)
     (features (auth logging)))
""")

val host = config.getStringValue("server.host")      // "localhost"
val port = config.getIntValue("server.port")         // 8080
val debug = config.getBooleanValue("server.debug")   // true
val features = config.getStringListValue("server.features") // ["auth", "logging"]

Read and write INI/configuration files with comprehensive parsing support.

Inspired by Python's configparser module.

Key features:

  • 📖 Parse INI files from strings, readers, or files
  • 🔄 Support for different INI dialects (standard, permissive, properties-style)
  • 🔒 Type-safe configuration access with property delegates
  • 🔤 Case-sensitive and case-insensitive parsing
  • 📝 Support for multiline values and interpolation
  • 🛡️ Comprehensive error handling

Example usage:

// Parse INI content
val iniContent = """
    app_name=My Web App
    debug=true
    port=8080
    
    [database]
    host=localhost
    port=5432
    ssl=true
""".trimIndent()

val iniFile = iniContent.parseIni()

// Basic access
val appName = iniFile.get("DEFAULT", "app_name")  // "My Web App"
val dbHost = iniFile.get("database", "host")      // "localhost"

// Type-safe property delegates
class AppConfig(iniFile: IniFile) {
    val appName by iniString(iniFile, defaultValue = "Default App")
    val debug by iniBoolean(iniFile, defaultValue = false)
    val port by iniInt(iniFile, defaultValue = 8080)
    val dbHost by iniString(iniFile, section = "database", key = "host")
}

val config = AppConfig(iniFile)
println("App: ${config.appName}, Debug: ${config.debug}")

Read and write CSV files with CsvReader and CsvWriter.

Inspired by python's csv module.

Key features:

  • 📖 Parse CSV from strings, readers, or files
  • ✍️ Write CSV using fluent API
  • 🔄 Support for different CSV dialects (Excel, Unix, custom)
  • ⚙️ Configurable delimiters, quoting, and escaping
  • 📚 Dictionary-based reading and writing
  • 🛡️ Proper handling of quoted fields and line breaks

Example usage:

// Reading CSV
val csvData = """
    name,age,city
    John,25,New York
    Jane,30,"Los Angeles"
    Bob,35,Chicago
""".trimIndent()

// Parse CSV rows
val rows = csvData.parseCsv().toList()
val headers = rows[0]  // ["name", "age", "city"]
val people = rows.drop(1)

// Using callback-based parsing
csvData.parseCsv { row ->
    if (row.lineNumber > 1) {  // Skip header
        println("${row.fields[0]} is ${row.fields[1]} years old")
    }
}

// Dictionary-based reading
val dictReader = csvData.parseCsvDict()
dictReader.forEach { record ->
    println("${record["name"]} lives in ${record["city"]}")
}

// Writing CSV
val output = buildCsvString {
    writeRow("Name", "Score", "Grade")
    writeRow("Alice", "95", "A")
    writeRow("Bob", "87", "B")
}

// Custom dialect
val tsvData = "name\tage\tcity\nJohn\t25\tNYC"
val tsvRows = tsvData.parseCsv(CsvDialect(delimiter = '\t')).toList()

Read and write TOML files with full support for TOML v1.0.0 specification.

Features:

  • 📖 Parse TOML documents from strings, readers, or files
  • ✍️ Write TOML documents using a Kotlin DSL
  • 🔢 Support for all TOML data types (strings, integers, floats, booleans, dates, arrays, tables)
  • 📋 Inline tables and array of tables
  • 💬 Comments and multi-line strings
  • 🔤 Proper escaping and Unicode support
  • 🔗 Idiomatic Kotlin API with extension functions

Example usage:

// Reading TOML
val document = """
    title = "My App"
    [database]
    host = "localhost"
    port = 5432
""".parseToml()

val title = document.getString("title")
val dbTable = document.getTable("database")

// Writing TOML
val toml = buildTomlString {
    string("title", "My App")
    table("database") {
        string("host", "localhost")
        integer("port", 5432)
    }
}

🏷️ Properties

Property delegation helpers for reading values from maps with type conversion.

Key features:

  • 🔒 Type-safe property delegates for common types (String, Int, Long, Double, Float, Boolean)
  • ❓ Support for nullable and non-nullable properties
  • 🔧 Default value handling
  • 📄 JSON property delegates for complex types

Example usage:

// Configuration from a map
val config = mapOf(
    "app_name" to "My Application",
    "port" to "8080",
    "debug" to "true",
    "timeout" to "30.5"
)

class AppSettings {
    // Non-nullable properties with defaults
    val appName by StringRO(config) { "Default App" }
    val port by IntRO(config) { 3000 }
    val debug by BooleanRO(config) { false }
    val timeout by DoubleRO(config) { 60.0 }
    
    // Nullable properties
    val theme by StringRONull(config)
    val maxConnections by IntRONull(config)
}

val settings = AppSettings()
println("${settings.appName} running on port ${settings.port}")
println("Debug mode: ${settings.debug}, Timeout: ${settings.timeout}s")

// JSON property delegates
val jsonConfig = mapOf("user" to """{"name": "John", "age": 30}""")

class UserConfig {
    val user by JsonRO<User>(jsonConfig)
}

Sequence utility functions, inspired by python's itertools module.

Key features:

  • ♾️ Infinite sequence generators (count, cycle, repeat)
  • ✂️ Sequence manipulation (islice, takeWhile, dropWhile)
  • 👯 Iterator duplication (tee)
  • 😴 Lazy evaluation with Kotlin sequences

Example usage:

// Infinite sequences
val numbers = count(1, 2).take(5).toList()  // [1, 3, 5, 7, 9]
val cycling = listOf("A", "B", "C").cycle().take(7).toList()  // [A, B, C, A, B, C, A]
val repeated = "hello".repeat(3).toList()  // ["hello", "hello", "hello"]

// Sequence slicing
val data = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
val slice = islice(data, 2, 8, 2).toList()  // [2, 4, 6] (start=2, stop=8, step=2)

// Conditional iteration
val numbers2 = listOf(1, 2, 3, 4, 5, 6, 7)
val lessThanFive = numbers2.asSequence().takeWhile { it < 5 }.toList()  // [1, 2, 3, 4]
val afterThree = numbers2.asSequence().dropWhile { it <= 3 }.toList()  // [4, 5, 6, 7]

// Iterator duplication
val original = listOf(1, 2, 3, 4)
val (iter1, iter2) = original.tee()
val sum1 = iter1.sum()  // 10
val sum2 = iter2.sum()  // 10 (independent iterator)

// Practical example: pagination
fun <T> List<T>.paginate(pageSize: Int) = 
    chunked(pageSize).mapIndexed { page, items ->
        "Page ${page + 1}: $items"
    }

val items = (1..20).toList()
items.paginate(5).forEach { println(it) }

Simple statistics functionality, inspired by python's statistics module.

Key features:

  • 📊 Basic statistical measures (mean, median, mode, variance, standard deviation)
  • 🔢 Support for different numeric types
  • 🔑 Key function support for complex data types
  • 🛡️ Robust error handling for edge cases

Example usage:

// Basic statistics
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

val average = mean(numbers)           // 5.5
val middle = median(numbers)          // 5.5
val spread = stdev(numbers)           // 3.03 (standard deviation)
val variance = variance(numbers)      // 9.17

// Working with different data types
val scores = listOf(85.5, 92.0, 88.5, 91.0, 87.5)
val avgScore = mean(scores)           // 88.9

// Using key functions for complex types
data class Student(val name: String, val grade: Int)
val students = listOf(
    Student("Alice", 95),
    Student("Bob", 87),
    Student("Charlie", 92)
)

val avgGrade = mean(students) { it.grade }        // 91.33
val medianGrade = median(students) { it.grade }   // 92.0

// Mode (most frequent value)
val grades = listOf(85, 90, 85, 92, 90, 85)
val mostCommon = mode(grades)         // 85

// Robust statistics
val dataWithOutliers = listOf(1, 2, 3, 4, 5, 100)
val robustMedian = median(dataWithOutliers)  // 3.5 (not affected by outlier)
val affectedMean = mean(dataWithOutliers)    // 19.17 (affected by outlier)

Simple function cache (Cache) and thread-safe LRU caches (LruCacheSync/LruCacheAsync).

Inspired by the similar function caches of python's functools module.

Key features:

  • 🗄️ Simple unbounded cache wrapper for functions
  • 🔒 Thread-safe LRU caches with configurable size limits
  • ⚡ Async cache for coroutines
  • 📊 Cache statistics and pre-warming
  • 🗑️ Manual cache eviction and clearing

Example usage:

// Simple function caching
fun expensiveCalculation(n: Int): Long = 
    if (n <= 1) 1L else expensiveCalculation(n - 1) + expensiveCalculation(n - 2)

val cachedFib = Cache(::expensiveCalculation)

// First call computes and caches
val fib10 = cachedFib(10)  // Slow first time
val fib10Again = cachedFib(10)  // Fast from cache

println("Cache stats: ${cachedFib.hitRate()}%")

// LRU Cache with size limit
val lruCache = LruCacheSync<String, String>(maxSize = 100) { key ->
    "Processed: $key"
}

val result = lruCache("input")  // Computes and caches
val cached = lruCache("input")  // Retrieved from cache

// Pre-warm cache
lruCache.prewarm(listOf("key1", "key2", "key3"))

// Async cache for coroutines
val asyncCache = LruCacheAsync<Int, String>(maxSize = 50) { key ->
    delay(100)  // Simulate async work
    "Result for $key"
}

// Usage in coroutines
runBlocking {
    val asyncResult = asyncCache(42)
    println(asyncResult)  // "Result for 42"
}

// Manual cache management
cachedFib.evict(10)      // Remove specific entry
cachedFib.clear()        // Clear all entries
lruCache.evictAll()      // Clear LRU cache

Shell-like command line parsing and lexical analysis.

Inspired by Python's shlex module.

Key features:

  • 🔤 Split shell command lines into tokens
  • 📝 Support for POSIX and non-POSIX parsing modes
  • 🔤 Handle quotes, escapes, and whitespace appropriately
  • 💬 Optional comment parsing
  • ⚙️ Configurable word characters, quotes, and escape sequences
  • 🛡️ Shell-safe string quoting and escaping

Example usage:

// Split shell command line
val tokens = split("ls -la 'my file.txt'")  // ["ls", "-la", "my file.txt"]

// Quote strings for shell safety
val quoted = quote("file with spaces.txt")  // "'file with spaces.txt'"

// Custom lexer with configuration
val lexer = ShlexLexer("command --flag='value with spaces'", posix = true, comments = false)
val customTokens = lexer.tokenize()

// Handle different quote styles
val mixedQuotes = split("""echo "Hello 'world'" 'How are "you"?'""")

Time parsing and formatting utilities for various date-time formats.

Key features:

  • 📅 Parse multiple date-time formats (ISO 8601, RFC 1123)
  • 🔄 Convert between LocalDateTime and OffsetDateTime
  • 🌍 Handle time zone corrections automatically
  • 📤 Export to ISO 8601 and RFC 1123 formats
  • 🛡️ Robust parsing with error handling

Example usage:

// Parse various time formats
val isoTime = "2024-07-16T09:38:41.694763+02:00".parseTime()
val rfcTime = "Tue, 16 Jul 2024 07:38:41 GMT".parseTime()
val localTime = "2024-07-16T09:38:41".parseTime()

// Convert to different formats
val iso8601 = localTime.toIso8601()     // "2024-07-16T09:38:41.694763+02:00"
val rfc1123 = localTime.toRfc1123()     // "Tue, 16 Jul 2024 07:38:41 GMT"

// Time zone handling
val offsetTime = localTime.toOffsetDateTime()
val corrected = offsetTime.toCorrectedLocalDateTime()

Kotlinx.serialization support for temporal types.

Key features:

  • 📅 Serializers for LocalDate, LocalDateTime, and Instant
  • 🔄 ISO 8601 format for serialization/deserialization
  • 🔗 Seamless integration with kotlinx.serialization
  • ⚙️ Ready-to-use serializer objects

Example usage:

@Serializable
data class Event(
    @Serializable(with = DateSerializer::class)
    val date: LocalDate,
    
    @Serializable(with = DateTimeSerializer::class)
    val timestamp: LocalDateTime,
    
    @Serializable(with = InstantSerializer::class)
    val created: Instant
)

// Automatic serialization to ISO format
val event = Event(
    date = LocalDate.now(),
    timestamp = LocalDateTime.now(),
    created = Instant.now()
)
val json = Json.encodeToString(event)

About

Missing parts of Kotlin's standard library

Resources

License

Stars

Watchers

Forks

Languages