Giter VIP home page Giter VIP logo

kapshot's Introduction

Kapshot

Kapshot is a simple Kotlin compiler plugin for capturing source code text from closure blocks and declarations.

Usage

Include the io.koalaql.kapshot-plugin Gradle plugin in your build.gradle.kts:

plugins {
    /* ... */

    id("io.koalaql.kapshot-plugin") version "0.1.2"
}

Capturing Blocks

Now your Kotlin code can use CapturedBlock<T> as a source enriched replacement for () -> T. You can use the source property on any instance of CapturedBlock to access the source for that block.

import io.koalaql.kapshot.CapturedBlock

val captured = CapturedBlock {
    println("Hello!")
}

check(captured.source.text == """println("Hello!")""")

You can invoke the block similar to a regular function:

import io.koalaql.kapshot.CapturedBlock

fun equation(block: CapturedBlock<Int>): String {
    val result = block() // invoke the block

    return "${block.source} = $result"
}

check(equation { 2 + 2 } == "2 + 2 = 4")

Parameterized Blocks

The default CapturedBlock interface doesn't accept any arguments to invoke and is only generic on the return type. This means the captured source block must depend only on state from the enclosing scope. To write source capturing versions of builder blocks or common higher-order functions like map and filter you will need to define your own capture interface that extends Capturable.

/* must be a fun interface to support SAM conversion from blocks */
fun interface CustomCapturable<T, R> : Capturable<CustomCapturable<T, R>> {
    /* invoke is not special. this could be any single abstract method */
    operator fun invoke(arg: T): R

    /* withSource is called by the plugin to add source information */
    override fun withSource(source: Source): CustomCapturable<T, R> =
        object : CustomCapturable<T, R> by this { override val source = source }
}

Once you have declared your own Capturable you can use it in a similar way to CapturedBlock from above.

fun <T> List<T>.mapped(block: CustomCapturable<T, T>): String {
    return "$this.map { ${block.source} } = ${map { block(it) }}"
}

check(
    listOf(1, 2, 3).mapped { x -> x*2 } ==
    "[1, 2, 3].map { x -> x*2 } = [2, 4, 6]"
)

If it is present, the block's argument list is considered part of its source text.

Declarations

You can capture declaration sources using the @CaptureSource annotation. The source of annotated declarations can then be retrieved using sourceOf<T> for class declarations or sourceOf(::declaration) for method and property declarations. The source capture starts at the end of the @CaptureSource annotation.

@CaptureSource
class MyClass {
    @CaptureSource
    fun twelve() = 12
}

check(
    sourceOf<MyClass>().text ==
    """
    class MyClass {
        @CaptureSource
        fun twelve() = 12
    }
    """.trimIndent()
)

check(
    sourceOf(MyClass::twelve).text ==
    "fun twelve() = 12"
)

Source Location

The Source::location property contains information about the location of captured source code including the file path (relative to the project root directory) and the char, line and column offsets for both the start and end of the captured source. Offsets are 0-indexed.

val source = CapturedBlock { 2 + 2 }.source
val location = source.location

println(
    "`${source.text}`"
    + " found in ${location.path}"
    + " @ line ${location.from.line+1}"
)

The code above will print the following:

`2 + 2` found in src/main/kotlin/Main.kt @ line 176

Purpose

The purpose of this plugin is to support experimental literate programming and documentation generation techniques in Kotlin

An example of this is the code used to generate this README.md. Capturing source from blocks allows sample code to be run and tested during generation.

View the source here: readme/src/main/kotlin/Main.kt

kapshot's People

Contributors

mfwgenerics avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

kapshot's Issues

possible incompatibility with the gatlin plugin

Firstly, thanks for some excellent libraries, very useful!

There seems to be an incompatibility with the gatlin plugin:

> Task :x:x:compileGatlingKotlin FAILED
e: java.util.NoSuchElementException: List is empty.
	at kotlin.collections.CollectionsKt___CollectionsKt.first(_Collections.kt:214)
	at kotlin.collections.CollectionsKt___CollectionsKt.first(_Collections.kt:197)
	at io.koalaql.kapshot.plugin.GenerationExtension.generate(GenerationExtension.kt:22)
	at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr$lambda$0(JvmIrCodegenFactory.kt:190)
	at org.jetbrains.kotlin.psi2ir.Psi2IrTranslator.generateModuleFragment(Psi2IrTranslator.kt:107)
	at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr(JvmIrCodegenFactory.kt:224)
	at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.convertToIr(JvmIrCodegenFactory.kt:57)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.convertToIr(KotlinToJVMBytecodeCompiler.kt:225)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:102)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:47)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:168)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:53)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:100)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:46)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
	at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:495)
	at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:133)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:486)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:409)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:290)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:112)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:627)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:101)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1587)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:359)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:691)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:832)
------------------------------------------------------------
Gradle 7.6
------------------------------------------------------------

Build time:   2022-11-25 13:35:10 UTC
Revision:     daece9dbc5b79370cc8e4fd6fe4b2cd400e150a8

Kotlin:       1.7.10
Groovy:       3.0.13
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.3 (Amazon.com Inc. 17.0.3+6-LTS)
OS:           Mac OS X 13.4.1 x86_64

> java -version
openjdk version "17.0.3" 2022-04-19 LTS
OpenJDK Runtime Environment Corretto-17.0.3.6.1 (build 17.0.3+6-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.3.6.1 (build 17.0.3+6-LTS, mixed mode, sharing)

To replicate:

  • be me
  • have a project with a gatling plugin and some gatling tests in the gatling module
  • also have a kotlin main module
  • project also uses the kapshot plugin
plugins {
   id("io.koalaql.kapshot-plugin") version "0.1.2"
}
  • mfw try to run the gatling tests using gradlew gatlingRun

I haven't looked too deeply at the issue, but it seems to me this could be easily fixed by not using first and using firstOrNull where the generator is trying to, er, generate.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.