Skip to content

Latest commit

 

History

History
346 lines (252 loc) · 9.72 KB

README.md

File metadata and controls

346 lines (252 loc) · 9.72 KB

API Development

This ReadMe provides an overview of the TextExplorer API and guides you through developing your own plugin. A sample implementation is available in plugins/demo.

While using a build tool is not mandatory, this documentation employs Gradle in its examples to align with the project's overall setup.

Environment setup

  1. Install Java 21.
  2. Create a new Gradle (Kotlin DSL) project.
  3. Download the latest API release and add the JAR as a local dependency:
implementation(files("libs/api-0.40.2.jar"))
  1. Reload the Gradle project.

First plugin

To create your first plugin, follow these steps:

  1. Create a Kotlin class, e.g., PluginMain, and implement the IPlugin interface:
class PluginMain : IPlugin {
    override fun init(storageProvider: IStorageProvider) {
        println("Hello world from plugin.")
    }
}

The init function is executed when the application starts or the plugin is loaded via the interface. Use this function to initialize external services or perform setup tasks. The StorageProvider instance can be used to access imported data.

  1. Annotate the class with @PluginMetadata to declare the plugin's metadata. You can include additional information like the version, author, or a short description.

  2. Optionally, use the @PluginOrder annotation to specify the load order for the plugin.

  3. Update the Gradle jar task to include the plugin's main class in the JAR file manifest:

tasks.jar {
    manifest {
        attributes["Main-Class"] = "PluginMain" // Adjust the path if necessary
    }
}

Provide data

To connect existing data to your plugin, annotate it with @RequiresData and specify the identifier of the data pool. For example, to connect to demo_data, annotate the class with: @RequiresData("demo_data").

To import your own data, a "data-layout" is required. It's recommended to create a separate Kotlin file (e.g., Data.kt) containing the relevant classes for the data structure.

Consider the following CSV files:

authors.csv:

author_id,full_name,year_born,birth_place
...,...,...,...
...

quotes.csv:

quote,date,author_id
...,...,...,...
...

In Data.kt, define your data classes to match the column names in the CSV files:

@DataSource("authors")
data class Author(
    @Unique(true) val author_id: Long,
    val full_name: String,
    val year_born: String
)

@DataSource("quotes")
data class Quote(
    @Index(Language.ENGLISH) val quote: String,
    val date: String,
    @Link(Author::class) val author_id: Long
)
  • @DataSource: Links the respective CSV file to the class. Ensure the class name matches the CSV name (the file extension is optional).

  • @Unique: Marks the field as unique. If true and the type is Long, it will serve as the identifier and will not be auto-generated by TextExplorer.

  • @Index: Marks the field as indexed in the specified language, which is essential for search functionality. It can be marked as the default value and must occur at least once in any class.

  • @Link: Establishes a relationship between two classes. Use this with @Index to enable related data display in the interface. The field name must be the same in both classes.

Finally, update the plugin to include the created data classes as part of its data requirements:

@RequiresData("demo_data", sources = [Author::class, Quote::class])
class PluginMain : IPlugin {
    ...
}

This will import the data appropriately, making it available for use in TextExplorer.

Currently only data in csv-files is supported.

Variants

Different terms can refer to the same entity, such as "USA," "US," "United States," or "America" for the country. TextExplorer provides the Variant API to handle these cases.

With the Variant API, you can define a column in a CSV file as the "base" term (the one the user will search for), and additional columns can store variant terms. This allows users to search for any variant.

Example: Countries

countries.csv

base,variant
USA,USA
USA,US
USA,United States
USA,America
...

In this example, "USA" is the base term, and the other terms are variants. The TextExplorer representation would look like this:

@Variant(base = "base", ["variant"])
@DataSource("countries")
data class Country(val base: String, val variant: String)

To connect this data to your plugin, you need to update the @RequiresData annotation to include the Country class:

@RequiresData("countries", sources = [Country::class, Author::class, Quote::class])
class PluginMain : IPlugin {
    ...
}

This setup allows the user to search for @countries:USA, and TextExplorer will return all entries that match any of the variants declared for "USA."

Pre filtering

Pre-filters can be used to select objects matching a condition before searching with the initial query. For this purpose, a data class needs to be annotated with @PreFilter and must have a link to another class that stores additional information. One can say a pre-filter acts on datasets, resembling a bridge between two data sets.

An example could be a set of entries where information is stored indicating whether a quote contains certain words:

occurrence.csv

quote_id,occurrence,word_id
77,false,12
78,true,12

words.csv

word_id,word,type
12,House,Noun

quotes.csv

quote_id,quote,author  
...
77,...,...
78,...,...
...

For this example a reference implementation would look like this:

@PreFilter(key = "quote_id", linkKey = "type", value = "occurrence")
@DataSource("occurrences")
data class Occurrence(val quote_id: Long, val occurrence: Int, @Link(Word::class) val word_id: Int)

@DataSource("words")
data class Word(val word_id: Long, val word: String, val type: String)

@DataSource("quotes")
data class Quote(@Unique(true) val quote_id: Long, @Index(Language.ENGLISH) val quote: String, val author: String)

@PreFilter has three parameters: key, linkKey, and value. The key field must match the identifier of a class that has a field marked with @Index, which will be used to retrieve the actual objects. linkKey determines the column for the first value in the request and must match a field from the linked class (e.g., Word in this example). The last parameter, value, specifies the column in the pre-filter dataset.

A query for this example would look like this: @occurrences:Noun:false. This query would return the quote with the quote_id 77.

Tagging API

The Tagging API allows you to highlight specific words (e.g., names) within the Tagging View.

To implement this functionality, simply implement the tag function from the Taggable interface in your Plugin Main class. This function takes the field name and its corresponding value as parameters.

Additionally, annotate the function with @ViewFilter, which specifies a filter name and the fields it accepts. The alwaysShow field can be used to make certain columns always visible Optionally, you can use the global parameter to apply the tags to the DiffView.

To highlight the name "Tom" in every field, the implementation would look like this:

@ViewFilter("Name Highlighter", fields = ["quote"], alwaysShow = ["author_id"], global = true)
override fun tag(field: String, value: String): Map<String, Tag> = mapOf("Tom" to Tag("NAME", Color.blue))

In this example:

  • Only the quote field is passed to the tag function.
  • The word "Tom" is mapped to the Tag with the identifier NAME and the color blue.
  • Every occurrence of "Tom" in the quote field will be highlighted in blue.

Drawable

The Drawable interface allows you to extend the user interface of your application. Since UI extension capabilities vary based on the UI framework in use, this interface does not include any pre-defined functions. However, when using the standard UI implementation with Compose, the UI will invoke functions like:

fun composeContent(entries: List<Map<String, String>>): @Composable () -> Unit = {
    //Compose components
}

to extend the user interface.

Important: The Drawable interface and the exact structure of the function are required.

Export plugin

To export the plugin, run the Gradle jar task:

./gradlew jar

For Windows use:

gradlew.bat jar

A full example:

PluginMain:

@PluginOrder(3)
@RequiresData("demo_data", sources = [Country::class, Author::class, Quote::class])
@PluginMetadata("demo", author = "Author", version = "1.0.0", description = "A short description.")
class PluginMain : IPlugin, Taggable {

    override fun init(storageProvider: IStorageProvider) {
        println("Hello world from plugin.")
    }

    @ViewFilter("Name Highlighter", fields = ["quote"], alwaysShow = ["author_id"], global = true)
    override fun tag(field: String, value: String): Map<String, Tag> = mapOf("Tom" to Tag("NAME", Color.blue))
}

Data.kt:

@Variant(base = "base", ["variant"])
@DataSource("countries")
data class Country(val base: String, val variant: String)

@DataSource("authors")
data class Author(
    @Unique(true) val author_id: Long,
    val full_name: String,
    val year_born: String
)

@DataSource("quotes")
data class Quote(
    @Index(Language.ENGLISH) val quote: String,
    val date: String,
    @Link(Author::class) val author_id: Long
)

build.gradle.kts

plugins {
    kotlin("jvm") version "2.0.21"
}

group = "com.example"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation(files("libs/api-0.32.2.jar"))
}

tasks.jar {
    manifest {
        attributes["Main-Class"] = "PluginMain"
    }
}

kotlin {
    jvmToolchain(21)
}