diff options
author | Błażej Kardyś <bkardys@virtuslab.com> | 2020-08-03 03:57:48 +0200 |
---|---|---|
committer | Paweł Marks <Kordyjan@users.noreply.github.com> | 2020-08-21 12:16:24 +0200 |
commit | c2b02c1fc17b839075b7cb6fd42498a519473fae (patch) | |
tree | 1de744003a37fbf361f799429ec9fb8887382037 /plugins/javadoc/src/main | |
parent | 69d16856abcc68b31d298e6a62a7a58106de69c9 (diff) | |
download | dokka-c2b02c1fc17b839075b7cb6fd42498a519473fae.tar.gz dokka-c2b02c1fc17b839075b7cb6fd42498a519473fae.tar.bz2 dokka-c2b02c1fc17b839075b7cb6fd42498a519473fae.zip |
Deprecated elements page for javadoc format
Diffstat (limited to 'plugins/javadoc/src/main')
10 files changed, 240 insertions, 46 deletions
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt index 01adf767..a8e10db5 100644 --- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt @@ -28,7 +28,8 @@ open class JavadocPageCreator( name = m.name.ifEmpty { "root" }, content = contentForModule(m), children = m.packages.map { pageForPackage(it) }, - dri = setOf(m.dri) + dri = setOf(m.dri), + extra = ((m as? WithExtraProperties<DModule>)?.extra ?: PropertyContainer.empty()) ) fun pageForPackage(p: DPackage) = diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt index eb0b7b97..48d6120f 100644 --- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt @@ -8,10 +8,7 @@ import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.renderers.PackageListCreator import org.jetbrains.dokka.base.renderers.RootCreator import org.jetbrains.dokka.base.resolvers.shared.RecognizedLinkFormat -import org.jetbrains.dokka.javadoc.pages.AllClassesPageInstaller -import org.jetbrains.dokka.javadoc.pages.IndexGenerator -import org.jetbrains.dokka.javadoc.pages.ResourcesInstaller -import org.jetbrains.dokka.javadoc.pages.TreeViewInstaller +import org.jetbrains.dokka.javadoc.pages.* import org.jetbrains.dokka.kotlinAsJava.KotlinAsJavaPlugin import org.jetbrains.dokka.plugability.DokkaPlugin import org.jetbrains.dokka.plugability.querySingle @@ -86,5 +83,9 @@ class JavadocPlugin : DokkaPlugin() { val indexGenerator by extending { javadocPreprocessors with IndexGenerator order { before(rootCreator) } } + + val deprecatedPageCreator by extending { + javadocPreprocessors with DeprecatedPageCreator order { before(rootCreator) } + } } diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt index 68836b46..984ee0e7 100644 --- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt @@ -34,6 +34,8 @@ class JavadocLocationProvider(pageRoot: RootPageNode, dokkaContext: DokkaContext listOf(packagePath, "package-summary") else if (page is IndexPage) listOf("index-files", page.name) + else if (page is DeprecatedPage) + listOf("deprecated") else listOf("index") else -> emptyList() @@ -85,22 +87,6 @@ class JavadocLocationProvider(pageRoot: RootPageNode, dokkaContext: DokkaContext "${resolve(it, context, skipExtension = true)}.html#$anchor" } - private fun JavadocFunctionNode.getAnchor(): String = - "$name(" + - parameters.joinToString(",") { - when (val bound = - if (it.typeBound is org.jetbrains.dokka.model.Nullable) it.typeBound.inner else it.typeBound) { - is TypeConstructor -> bound.dri.classNames.orEmpty() - is TypeParameter -> bound.name - is PrimitiveJavaType -> bound.name - is UnresolvedBound -> bound.name - is JavaObject -> "Object" - else -> bound.toString() - } - } + ")" - - fun anchorForFunctionNode(node: JavadocFunctionNode) = node.getAnchor() - private fun anchorForDri(dri: DRI): String = dri.callable?.let { callable -> "${callable.name}(${ diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt index 31d879a2..de07b730 100644 --- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt @@ -15,6 +15,8 @@ import org.jetbrains.dokka.pages.* import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.resolve.DescriptorUtils.getClassDescriptorForType +import java.util.* +import kotlin.collections.HashMap interface JavadocPageNode : ContentPage @@ -24,7 +26,7 @@ interface WithJavadocExtra<T : Documentable> : WithExtraProperties<T> { } interface WithIndexables { - fun getAllIndexables(): List<IndexableJavadocNode> + fun getAllIndexables(): List<NavigableJavadocNode> } interface WithBrief { @@ -35,15 +37,19 @@ class JavadocModulePageNode( override val name: String, override val content: JavadocContentNode, override val children: List<PageNode>, - override val dri: Set<DRI> + override val dri: Set<DRI>, + override val extra: PropertyContainer<DModule> = PropertyContainer.empty() ) : RootPageNode(), - JavadocPageNode, ModulePage { + WithJavadocExtra<DModule>, + NavigableJavadocNode, + JavadocPageNode, + ModulePage { override val documentable: Documentable? = null override val embeddedResources: List<String> = emptyList() override fun modified(name: String, children: List<PageNode>): RootPageNode = - JavadocModulePageNode(name, content, children, dri) + JavadocModulePageNode(name, content, children, dri, extra) override fun modified( name: String, @@ -51,7 +57,11 @@ class JavadocModulePageNode( dri: Set<DRI>, embeddedResources: List<String>, children: List<PageNode> - ): ContentPage = JavadocModulePageNode(name, content as JavadocContentNode, children, dri) + ): ContentPage = JavadocModulePageNode(name, content as JavadocContentNode, children, dri, extra) + + override fun getId(): String = name + + override fun getDRI(): DRI = dri.first() } class JavadocPackagePageNode( @@ -62,10 +72,13 @@ class JavadocPackagePageNode( override val documentable: Documentable? = null, override val children: List<PageNode> = emptyList(), override val embeddedResources: List<String> = listOf() -) : JavadocPageNode, WithIndexables, IndexableJavadocNode, PackagePage { +) : JavadocPageNode, + WithIndexables, + NavigableJavadocNode, + PackagePage { - override fun getAllIndexables(): List<IndexableJavadocNode> = - children.filterIsInstance<IndexableJavadocNode>().flatMap { + override fun getAllIndexables(): List<NavigableJavadocNode> = + children.filterIsInstance<NavigableJavadocNode>().flatMap { if (it is WithIndexables) it.getAllIndexables() else listOf(it) } @@ -103,12 +116,12 @@ class JavadocPackagePageNode( override fun getDRI(): DRI = dri.first() } -interface IndexableJavadocNode { +interface NavigableJavadocNode { fun getId(): String fun getDRI(): DRI } -sealed class AnchorableJavadocNode(open val name: String, open val dri: DRI) : IndexableJavadocNode { +sealed class AnchorableJavadocNode(open val name: String, open val dri: DRI) : NavigableJavadocNode { override fun getId(): String = name override fun getDRI(): DRI = dri } @@ -172,9 +185,9 @@ class JavadocClasslikePageNode( override val children: List<PageNode> = emptyList(), override val embeddedResources: List<String> = listOf(), override val extra: PropertyContainer<DClasslike> = PropertyContainer.empty(), -) : JavadocPageNode, WithJavadocExtra<DClasslike>, IndexableJavadocNode, WithIndexables, WithBrief, ClasslikePage { +) : JavadocPageNode, WithJavadocExtra<DClasslike>, NavigableJavadocNode, WithIndexables, WithBrief, ClasslikePage { - override fun getAllIndexables(): List<IndexableJavadocNode> = + override fun getAllIndexables(): List<NavigableJavadocNode> = methods + entries + classlikes.map { it.getAllIndexables() }.flatten() + this val kind: String? = documentable?.kind() @@ -262,9 +275,59 @@ class AllClassesPage(val classes: List<JavadocClasslikePageNode>) : JavadocPageN } +class DeprecatedPage( + val elements: Map<DeprecatedPageSection, Set<DeprecatedNode>>, + sourceSet: Set<DisplaySourceSet> +) : JavadocPageNode { + override val name: String = "deprecated" + override val dri: Set<DRI> = setOf(DRI.topLevel) + override val documentable: Documentable? = null + override val children: List<PageNode> = emptyList() + override val embeddedResources: List<String> = listOf() + + override val content: ContentNode = EmptyNode( + DRI.topLevel, + ContentKind.Main, + sourceSet + ) + + override fun modified( + name: String, + children: List<PageNode> + ): PageNode = this + + override fun modified( + name: String, + content: ContentNode, + dri: Set<DRI>, + embeddedResources: List<String>, + children: List<PageNode> + ): ContentPage = this + +} + +class DeprecatedNode(val name: String, val address: DRI, val description: List<ContentNode>) { + override fun equals(other: Any?): Boolean = + (other as? DeprecatedNode)?.address == address + + override fun hashCode(): Int = address.hashCode() +} + +enum class DeprecatedPageSection(val id: String, val caption: String, val header: String, val priority: Int = 100) { + DeprecatedForRemoval("forRemoval", "For Removal", "Element", priority = 90), + DeprecatedModules("module", "Modules", "Module"), + DeprecatedInterfaces("interface", "Interfaces", "Interface"), + DeprecatedClasses("class", "Classes", "Class"), + DeprecatedEnums("enum", "Enums", "Enum"), + DeprecatedFields("field", "Fields", "Field"), + DeprecatedMethods("method", "Methods", "Method"), + DeprecatedConstructors("constructor", "Constructors", "Constructor"), + DeprecatedEnumConstants("enum.constant", "Enum Constants", "Enum Constant") +} + class IndexPage( val id: Int, - val elements: List<IndexableJavadocNode>, + val elements: List<NavigableJavadocNode>, val keys: List<Char>, sourceSet: Set<DisplaySourceSet> @@ -285,8 +348,7 @@ class IndexPage( override fun modified( name: String, children: List<PageNode> - ): PageNode = - TODO() + ): PageNode = this override fun modified( name: String, @@ -294,8 +356,7 @@ class IndexPage( dri: Set<DRI>, embeddedResources: List<String>, children: List<PageNode> - ): ContentPage = - TODO() + ): ContentPage = this } diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt index c54d484a..418327e6 100644 --- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt @@ -1,6 +1,10 @@ package org.jetbrains.dokka.javadoc.pages import org.jetbrains.dokka.base.renderers.sourceSets +import org.jetbrains.dokka.base.transformers.documentables.deprecatedAnnotation +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.StringValue import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.transformers.pages.PageTransformer import kotlin.collections.HashMap @@ -67,12 +71,12 @@ object AllClassesPageInstaller : PageTransformer { } } -object IndexGenerator: PageTransformer { +object IndexGenerator : PageTransformer { override fun invoke(input: RootPageNode): RootPageNode { - val elements = HashMap<Char, MutableSet<IndexableJavadocNode>>() + val elements = HashMap<Char, MutableSet<NavigableJavadocNode>>() (input as JavadocModulePageNode).children.filterIsInstance<JavadocPackagePageNode>().forEach { it.getAllIndexables().forEach { d -> - val name = when(d) { + val name = when (d) { is JavadocPageNode -> d.name is AnchorableJavadocNode -> d.name else -> null @@ -88,4 +92,61 @@ object IndexGenerator: PageTransformer { IndexPage(i + 1, set.sortedBy { it.getId().toLowerCase() }, keys, input.sourceSets()) }) } +} + +object DeprecatedPageCreator : PageTransformer { + override fun invoke(input: RootPageNode): RootPageNode { + val elements = HashMap<DeprecatedPageSection, MutableSet<DeprecatedNode>>().apply { + + fun <T> T.putAs(deprecatedPageSection: DeprecatedPageSection) where + T : NavigableJavadocNode, + T : WithJavadocExtra<out Documentable> { + val deprecatedNode = DeprecatedNode( + listOfNotNull( + getDRI().packageName?.takeUnless { it.isBlank() }, + if (this is JavadocFunctionNode) getAnchor() else getId() + ).joinToString("."), + getDRI(), + (this as? WithBrief)?.brief.orEmpty() + ) + getOrPut(deprecatedPageSection) { mutableSetOf() }.add(deprecatedNode) + if (deprecatedAnnotation?.params?.get("forRemoval") == StringValue("true")) { + getOrPut(DeprecatedPageSection.DeprecatedForRemoval) { mutableSetOf() }.add(deprecatedNode) + } + } + + fun verifyDeprecation(node: NavigableJavadocNode) { + when (node) { + is JavadocModulePageNode -> { + node.children.filterIsInstance<JavadocPackagePageNode>().forEach(::verifyDeprecation) + node.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedModules) + } + is JavadocPackagePageNode -> + node.children.filterIsInstance<NavigableJavadocNode>().forEach(::verifyDeprecation) + is JavadocClasslikePageNode -> { + node.classlikes.forEach(::verifyDeprecation) + node.methods.forEach { it.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedMethods) } + node.constructors.forEach { it.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedConstructors) } + node.properties.forEach { it.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedFields) } + node.entries.forEach { it.takeIf { it.isDeprecated() }?.putAs(DeprecatedPageSection.DeprecatedEnumConstants) } + node.takeIf { it.isDeprecated() }?.putAs( + when (node.kind) { + "enum" -> DeprecatedPageSection.DeprecatedEnums + "interface" -> DeprecatedPageSection.DeprecatedInterfaces + else -> DeprecatedPageSection.DeprecatedClasses + } + ) + } + } + } + + verifyDeprecation(input as JavadocModulePageNode) + } + return input.modified( + children = input.children + DeprecatedPage( + elements, + (input as JavadocModulePageNode).sourceSets() + ) + ) + } }
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/utils.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/utils.kt new file mode 100644 index 00000000..2b3005ab --- /dev/null +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/utils.kt @@ -0,0 +1,15 @@ +package org.jetbrains.dokka.javadoc.pages + +import org.jetbrains.dokka.model.* + +internal fun JavadocFunctionNode.getAnchor(): String = + "$name(${parameters.joinToString(",") { + when (val bound = if (it.typeBound is Nullable) it.typeBound.inner else it.typeBound) { + is TypeConstructor -> bound.dri.classNames.orEmpty() + is TypeParameter -> bound.name + is PrimitiveJavaType -> bound.name + is UnresolvedBound -> bound.name + is JavaObject -> "Object" + else -> bound.toString() + } + }})"
\ No newline at end of file diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt index 554bdb8a..88bd90c3 100644 --- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt @@ -37,6 +37,7 @@ internal class JavadocContentToTemplateMapTranslator( is TreeViewPage -> InnerTranslator(node).templateMapForTreeViewPage(node) is AllClassesPage -> InnerTranslator(node).templateMapForAllClassesPage(node) is IndexPage -> InnerTranslator(node).templateMapForIndexPage(node) + is DeprecatedPage -> InnerTranslator(node).templateMapForDeprecatedPage(node) else -> emptyMap() } @@ -74,6 +75,33 @@ internal class JavadocContentToTemplateMapTranslator( "elements" to node.elements.map { templateMapForIndexableNode(it) } ) + fun templateMapForDeprecatedPage(node: DeprecatedPage): TemplateMap = + mapOf( + "id" to node.name, + "title" to "Deprecated", + "kind" to "deprecated", + "sections" to node.elements.toList().sortedBy { (section, _) -> section.priority } + .map { (s, e) -> templateMapForDeprecatedPageSection(s, e) } + ) + + fun templateMapForDeprecatedPageSection( + section: DeprecatedPageSection, + elements: Set<DeprecatedNode> + ): TemplateMap = + mapOf( + "id" to section.id, + "header" to section.header, + "caption" to section.caption, + "elements" to elements.map { node -> + mapOf( + "name" to node.name, + "address" to locationProvider.resolve(node.address, contextNode.sourceSets(), contextNode) + ?.formatToEndWithHtml().orEmpty(), + "description" to htmlForContentNodes(node.description, contextNode) + ) + } + ) + fun templateMapForTreeViewPage(node: TreeViewPage): TemplateMap = mapOf( "title" to node.title, @@ -93,7 +121,7 @@ internal class JavadocContentToTemplateMapTranslator( "description" to htmlForContentNodes(node.description,contextNode), "parameters" to node.parameters.map { templateMapForParameterNode(it) }, "inlineParameters" to node.parameters.joinToString { renderInlineParameter(it) }, - "anchorLink" to locationProvider.anchorForFunctionNode(node), + "anchorLink" to node.getAnchor(), "signature" to templateMapForSignatureNode(node.signature), "name" to node.name ) @@ -121,7 +149,7 @@ internal class JavadocContentToTemplateMapTranslator( "supertypes" to node.supertypes?.let { htmlForContentNode(it, contextNode) } ) - private fun IndexableJavadocNode.typeForIndexable() = when (this) { + private fun NavigableJavadocNode.typeForIndexable() = when (this) { is JavadocClasslikePageNode -> "class" is JavadocFunctionNode -> "function" is JavadocEntryNode -> "enum entry" @@ -131,14 +159,14 @@ internal class JavadocContentToTemplateMapTranslator( else -> "" } - fun templateMapForIndexableNode(node: IndexableJavadocNode): TemplateMap { + fun templateMapForIndexableNode(node: NavigableJavadocNode): TemplateMap { val origin = node.getDRI().parent return mapOf( "address" to locationProvider.resolve(node.getDRI(), contextNode.sourceSets(), contextNode) ?.formatToEndWithHtml().orEmpty(), "type" to node.typeForIndexable(), "isMember" to (node !is JavadocPackagePageNode), - "name" to if (node is JavadocFunctionNode) locationProvider.anchorForFunctionNode(node) else node.getId(), + "name" to if (node is JavadocFunctionNode) node.getAnchor() else node.getId(), "description" to ((node as? WithBrief)?.let { htmlForContentNodes( it.brief, diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt index cdd045a4..2d0219a3 100644 --- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt +++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt @@ -52,6 +52,7 @@ class KorteJavadocRenderer(private val outputWriter: OutputWriter, val context: is AllClassesPage -> "listPage.korte" is TreeViewPage -> "treePage.korte" is IndexPage -> "indexPage.korte" + is DeprecatedPage -> "deprecated.korte" else -> "" } diff --git a/plugins/javadoc/src/main/resources/views/components/navList.korte b/plugins/javadoc/src/main/resources/views/components/navList.korte index d18b44c4..dbfe7eb3 100644 --- a/plugins/javadoc/src/main/resources/views/components/navList.korte +++ b/plugins/javadoc/src/main/resources/views/components/navList.korte @@ -21,7 +21,7 @@ {% else %} <li><a href="package-tree.html">Tree</a></li> {% end %} - <li>Deprecated</li> + <li><a href="{{ pathToRoot }}deprecated.html">Deprecated</a></li> <li><a href="{{ pathToRoot }}index-files/index-1.html">Index</a></li> <li>Help</li> </ul>
\ No newline at end of file diff --git a/plugins/javadoc/src/main/resources/views/deprecated.korte b/plugins/javadoc/src/main/resources/views/deprecated.korte new file mode 100644 index 00000000..a22e1069 --- /dev/null +++ b/plugins/javadoc/src/main/resources/views/deprecated.korte @@ -0,0 +1,40 @@ +{% extends "components/base.korte" %} +{% block content %} + +<main role="main"> + <div class="header"> + <h1 title="Deprecated API" class="title">Deprecated API</h1> + <h2 title="Contents">Contents</h2> + <ul> + {% for section in sections %} + <li><a href="#{{ section.id }}">{{ section.caption }}</a></li> + {% endfor %} + </ul> + </div> + <div class="contentContainer"> + {% for section in sections %} + <a id="{{ section.id }}"></a> + <ul class="blockList"> + <li class="blockList"> + <div class="deprecatedSummary"> + <table> + <caption><span>{{ section.caption }}</span><span class="tabEnd"> </span></caption> + <tr> + <th class="colFirst" scope="col">{{ section.header }}</th> + <th class="colLast" scope="col">Description</th> + </tr> + {% for element in section.elements %} + <tr class="{{ rowColor(loop.index0) }}"> + <th class="colDeprecatedItemName" scope="row"> + <a href="{{ element.address }}">{{ element.name }}</a> + </th> + <td class="colLast">{{ element.description|raw }}</td> + </tr> + {% endfor %} + </table> + </div> + </li> + </ul> + {% endfor %} + </div> +</main>
\ No newline at end of file |