Skip to content

Commit

Permalink
Improve usage of the directives in the api (#137)
Browse files Browse the repository at this point in the history
* Move directive implementations into an own package

* Improve base structure

* Improve create method

* Generalise directive write to have less duplicated code

* Remove constant variable

* Update import usage

* Update import usage
  • Loading branch information
theEvilReaper authored Jul 21, 2024
1 parent 1b5b889 commit 9f12410
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 78 deletions.
13 changes: 5 additions & 8 deletions src/main/kotlin/net/theevilreaper/dartpoet/DartFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import net.theevilreaper.dartpoet.clazz.ClassSpec
import net.theevilreaper.dartpoet.code.CodeWriter
import net.theevilreaper.dartpoet.code.WriterHelper
import net.theevilreaper.dartpoet.code.buildCodeString
import net.theevilreaper.dartpoet.directive.DartDirective
import net.theevilreaper.dartpoet.directive.ExportDirective
import net.theevilreaper.dartpoet.directive.LibraryDirective
import net.theevilreaper.dartpoet.directive.PartDirective
import net.theevilreaper.dartpoet.directive.RelativeDirective
import net.theevilreaper.dartpoet.directive.impl.DartDirective
import net.theevilreaper.dartpoet.directive.impl.ExportDirective
import net.theevilreaper.dartpoet.directive.impl.LibraryDirective
import net.theevilreaper.dartpoet.directive.impl.PartDirective
import net.theevilreaper.dartpoet.directive.impl.RelativeDirective
import net.theevilreaper.dartpoet.extension.ExtensionSpec
import net.theevilreaper.dartpoet.util.*
import net.theevilreaper.dartpoet.property.consts.ConstantPropertySpec
Expand All @@ -32,9 +32,7 @@ class DartFile internal constructor(
internal val extensions: List<ExtensionSpec> = builder.extensionStack
internal val docs = builder.docs
internal val constants: Set<ConstantPropertySpec> = builder.constants.toImmutableSet()

private val directives = builder.directives.toImmutableList()

internal val dartImports =
DirectiveOrdering.sortDirectives<DartDirective>(DartDirective::class, directives) { it.contains("dart:") }
internal val packageImports =
Expand All @@ -45,7 +43,6 @@ class DartFile internal constructor(
DirectiveOrdering.sortDirectives<ExportDirective>(ExportDirective::class, directives)
internal val relativeImports =
DirectiveOrdering.sortDirectives<RelativeDirective>(RelativeDirective::class, directives)

internal val typeDefs = builder.typeDefs.toImmutableList()
internal val hasTypeDefs = typeDefs.isNotEmpty()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import net.theevilreaper.dartpoet.util.DART_FILE_ENDING
* @since 1.0.0
*/
abstract class BaseDirective(
protected val type: DirectiveType,
private val path: String
) : Directive {

Expand Down Expand Up @@ -39,7 +40,7 @@ abstract class BaseDirective(
* Ensures that the directive path ends with .dart.
* @return the original string or the string with .dart at the end
*/
protected fun String.ensureDartFileEnding(): String {
private fun String.ensureDartFileEnding(): String {
return when (!this.endsWith(DART_FILE_ENDING) && !isDartImport()) {
true -> "$this$DART_FILE_ENDING"
false -> this
Expand All @@ -50,7 +51,7 @@ abstract class BaseDirective(
* Checks if a given import path starts with the word dart.
* @return true when the path starts with the word otherwise false
*/
protected fun isDartImport(): Boolean {
private fun isDartImport(): Boolean {
return path.startsWith("dart")
}

Expand All @@ -59,4 +60,16 @@ abstract class BaseDirective(
* @return the raw data string
*/
override fun getRawPath(): String = this.path

/**
* Returns the type of the directive.
* @return the [DirectiveType] of the directive
*/
override fun type(): DirectiveType = type

/**
* Returns the path of the directive with the ending .dart.
* @return the path with the ending
*/
override fun getPathWithEnding(): String = this.path.ensureDartFileEnding()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package net.theevilreaper.dartpoet.directive
*/
sealed interface Directive : Comparable<Directive> {

fun type(): DirectiveType

/**
* Returns a string representation of the directive.
* @return the string representation
Expand All @@ -29,4 +31,10 @@ sealed interface Directive : Comparable<Directive> {
* @return the raw path
*/
fun getRawPath(): String

/**
* Returns the path with the ending .dart.
* @return the path with the ending
*/
fun getPathWithEnding(): String
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.theevilreaper.dartpoet.directive

import net.theevilreaper.dartpoet.directive.impl.*

/**
* The [DirectiveFactory] should be used to create a new instance of different [Directive] implementations.
* It's required to use this factory to create a new instance because it will check if the given [DirectiveType].
Expand Down Expand Up @@ -73,13 +75,11 @@ object DirectiveFactory {
partOf: Boolean = false,
castType: CastType? = null,
importCast: String? = null,
): Directive {
return when (directive) {
DirectiveType.IMPORT -> DartDirective(path, castType, importCast)
DirectiveType.RELATIVE -> RelativeDirective(path, castType, importCast)
DirectiveType.PART -> PartDirective(path)
DirectiveType.LIBRARY -> LibraryDirective(path, partOf)
DirectiveType.EXPORT -> ExportDirective(path, castType, importCast)
}
): Directive = when (directive) {
DirectiveType.IMPORT -> DartDirective(path, castType, importCast)
DirectiveType.RELATIVE -> RelativeDirective(path, castType, importCast)
DirectiveType.PART -> PartDirective(path)
DirectiveType.LIBRARY -> LibraryDirective(path, partOf)
DirectiveType.EXPORT -> ExportDirective(path, castType, importCast)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package net.theevilreaper.dartpoet.directive

import net.theevilreaper.dartpoet.DartModifier
import net.theevilreaper.dartpoet.code.CodeWriter
import net.theevilreaper.dartpoet.directive.impl.DartDirective
import net.theevilreaper.dartpoet.util.SEMICOLON

/**
* The [DirectiveHelper] is a utility class that provides functionality to write a [Directive] to a [CodeWriter] instance.
*
* <p>In previous versions, the implementation for writing a directive was embedded within the directive implementations themselves.
* This approach resulted in duplicated code and violated the "Don't Repeat Yourself" (DRY) principle.</p>
*
* <p>This helper class centralizes the write process, reducing code duplication and providing a single point of maintenance for directive writing logic.</p>
*
* @version 1.0.0
* @since 1.0.0
* @author theEvilReaper
*/
internal object DirectiveHelper {

private const val IMPORT_KEY: String = "import"
private const val EXPORT_KEY: String = "export"
private const val PART_KEY: String = "part"

/**
* Writes a given [Directive] implementation to a [CodeWriter] instance.
* This method raises an [UnsupportedOperationException] if the directive is a library or part directive.
* @param writer the [CodeWriter] instance to append the directive
* @param directive the directive to write
* @param importCast the cast for the import
* @param castType the type of the cast
* @throws UnsupportedOperationException if the directive is a library or part directive
*/
@Throws(UnsupportedOperationException::class)
internal fun writeDirective(writer: CodeWriter, directive: Directive, importCast: String?, castType: CastType?) {
if (directive.type() == DirectiveType.LIBRARY || directive.type() == DirectiveType.PART) {
throw UnsupportedOperationException("The library and part directive should be written by the directive impl")
}

val importKey = when (directive.type()) {
DirectiveType.EXPORT -> EXPORT_KEY
else -> IMPORT_KEY
}

writer.emit(importKey)
writer.emitSpace()

val path = when (directive.type()) {
DirectiveType.IMPORT -> updateImportBegin(directive.getPathWithEnding())
else -> directive.getPathWithEnding()
}

writer.emitCode("%C", path)

if (importCast != null && castType != null) {
writer.emitSpace()
writer.emitCode("%L", castType.identifier)
writer.emitSpace()
writer.emitCode("%L", importCast)
}

writer.emit(SEMICOLON)
}

/**
* Writes a given [Directive] implementation to a [CodeWriter] instance.
* This method is only used to write the library and part directive.
* @param writer the [CodeWriter] instance to append the directive
* @param directive the directive to write
* @param asPartOf the flag to check if the library is part of another library
* @throws UnsupportedOperationException if the directive is not a part or library directive
*/
@Throws(UnsupportedOperationException::class)
internal fun writePartOrLibDirective(writer: CodeWriter, directive: Directive, asPartOf: Boolean = false) {
if (!(directive.type() == DirectiveType.PART || directive.type() == DirectiveType.LIBRARY)) {
throw UnsupportedOperationException("This method can only be used for part and library directive")
}

val importPair: Pair<String, String> = when (directive.type()) {
DirectiveType.PART -> Pair(PART_KEY, "%C")
else -> Pair(getLibraryImportKey(asPartOf), "%L")
}

writer.emitCode("%L", importPair.first)
writer.emitSpace()
writer.emitCode(importPair.second, directive.getRawPath())
writer.emit(SEMICOLON)
}

/**
* Returns the key for the library import.
* @param asPartOf the flag to check if the library is part of another library
* @return the key for the library import
*/
private fun getLibraryImportKey(asPartOf: Boolean = false): String = when (asPartOf) {
true -> "part of"
false -> DartModifier.LIBRARY.identifier
}

/**
* The method updates the given import path if it doesn't start with `dart:`.
* In this case, the method will add the `package:` prefix to the path.
* A `package:` import is required when the [DartDirective] is used to import a file from a package.
* @param path the path to update
* @return the updated path
*/
private fun updateImportBegin(path: String): String {
return when (path.startsWith("dart:")) {
true -> path
false -> "package:$path"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package net.theevilreaper.dartpoet.directive
package net.theevilreaper.dartpoet.directive.impl

import net.theevilreaper.dartpoet.code.CodeWriter
import net.theevilreaper.dartpoet.util.IMPORT
import net.theevilreaper.dartpoet.util.SEMICOLON
import net.theevilreaper.dartpoet.directive.BaseDirective
import net.theevilreaper.dartpoet.directive.CastType
import net.theevilreaper.dartpoet.directive.DirectiveHelper
import net.theevilreaper.dartpoet.directive.DirectiveType

/**
* Represents an import directive from dart which usual starts with `dart` or `package`.
Expand All @@ -16,10 +18,10 @@ import net.theevilreaper.dartpoet.util.SEMICOLON
* @constructor Creates a Dart import directive with the specified path as [String], a cast type as [CastType], and a importCast a [String].
*/
class DartDirective internal constructor(
private val path: String,
path: String,
private val castType: CastType? = null,
private val importCast: String? = null,
) : BaseDirective(path) {
) : BaseDirective(DirectiveType.IMPORT, path) {

/**
* Check if some conditions are false and throw an exception.
Expand All @@ -40,14 +42,6 @@ class DartDirective internal constructor(
* @param writer the writer instance to append the directive
*/
override fun write(writer: CodeWriter) {
writer.emit("$IMPORT ")
val ensuredPath = path.ensureDartFileEnding()
val pathToWrite = if (isDartImport()) ensuredPath else "package:$ensuredPath"
writer.emit("'$pathToWrite'")

if (importCast != null && castType != null) {
writer.emit(" ${castType.identifier} $importCast")
}
writer.emit(SEMICOLON)
DirectiveHelper.writeDirective(writer, this, importCast, castType)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package net.theevilreaper.dartpoet.directive
package net.theevilreaper.dartpoet.directive.impl

import net.theevilreaper.dartpoet.code.CodeWriter
import net.theevilreaper.dartpoet.util.SEMICOLON
import net.theevilreaper.dartpoet.directive.*

/**
* This implementation represents the export directive from dart.
Expand All @@ -13,17 +13,17 @@ import net.theevilreaper.dartpoet.util.SEMICOLON
* @author theEvilReaper
*/
class ExportDirective internal constructor(
private val path: String,
path: String,
private val castType: CastType? = null,
private val importCast: String? = null,
) : BaseDirective(path) {
) : BaseDirective(DirectiveType.EXPORT, path) {

private val export = "export"
private val invalidCastType = arrayOf(CastType.DEFERRED, CastType.AS)

init {
if (castType != null && castType in invalidCastType) {
throw IllegalStateException("The following cast types are not allowed for an export directive: [${invalidCastType.joinToString()}]")
check(!(castType != null && castType in invalidCastType)) {
"The following cast types are not allowed for an export directive: [${invalidCastType.joinToString()}]"
}

if (importCast != null) {
Expand All @@ -40,14 +40,6 @@ class ExportDirective internal constructor(
* @param writer the [CodeWriter] instance to append the directive
*/
override fun write(writer: CodeWriter) {
val ensuredPath = path.ensureDartFileEnding()
writer.emit("$export·'")
writer.emit(ensuredPath)
writer.emit("'")

if (importCast != null && castType != null) {
writer.emit("·${castType.identifier} $importCast")
}
writer.emit(SEMICOLON)
DirectiveHelper.writeDirective(writer, this, importCast, castType)
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
package net.theevilreaper.dartpoet.directive
package net.theevilreaper.dartpoet.directive.impl

import net.theevilreaper.dartpoet.DartModifier.LIBRARY
import net.theevilreaper.dartpoet.code.CodeWriter
import net.theevilreaper.dartpoet.util.SEMICOLON
import net.theevilreaper.dartpoet.directive.BaseDirective
import net.theevilreaper.dartpoet.directive.DirectiveHelper
import net.theevilreaper.dartpoet.directive.DirectiveType

/**
* The [LibraryDirective] represents the library directive from dart.
* @since 1.0.0
* @author theEvilReaper
*/
class LibraryDirective internal constructor(
private val path: String,
path: String,
private val asPartOf: Boolean = false
) : BaseDirective(path) {
) : BaseDirective(DirectiveType.LIBRARY, path) {

/**
* Writes the data from the [LibraryDirective] to a given instance from a [CodeWriter].
* @param writer the [CodeWriter] instance to append the directive
*/
override fun write(writer: CodeWriter) {
val baseString = if (asPartOf) "part of" else LIBRARY.identifier
writer.emit("$baseString·")
writer.emit(path)
writer.emit(SEMICOLON)
DirectiveHelper.writePartOrLibDirective(writer, this, asPartOf)
}
}
Loading

0 comments on commit 9f12410

Please sign in to comment.