From 92e6417ec5829f30401f82a32f1e6a4565e3c230 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Wed, 11 Nov 2020 01:23:07 +0100 Subject: [PATCH 1/2] New format of source linksNow we use liquid templates to generate link to source file.We use our custom-build support for handling this instead of one from dokka.This commit also adds predefined templates for github and gitlab as well ass 'revision setting' that allows users to specify revision of documented code (and source link) --- project/Build.scala | 6 +- .../src/dotty/dokka/DottyDokkaConfig.scala | 5 +- .../src/dotty/dokka/DottyDokkaPlugin.scala | 36 ++--- scala3doc/src/dotty/dokka/Main.scala | 9 +- scala3doc/src/dotty/dokka/SourceLinks.scala | 123 +++++++++++++++++ scala3doc/src/dotty/dokka/model/api/api.scala | 3 + .../dokka/model/api/internalExtensions.scala | 3 +- scala3doc/src/dotty/dokka/model/extras.scala | 14 -- .../src/dotty/dokka/model/scalaModel.scala | 4 - .../src/dotty/dokka/tasty/BasicSupport.scala | 6 +- .../dotty/dokka/tasty/ClassLikeSupport.scala | 18 ++- .../ScalaSourceLinksTransformer.scala | 61 --------- .../ScalaDocumentableToPageTranslator.scala | 16 --- .../dokka/translators/ScalaPageCreator.scala | 21 ++- scala3doc/test/dotty/dokka/ScaladocTest.scala | 3 +- .../test/dotty/dokka/SourceLinksTests.scala | 125 ++++++++++++++++++ 16 files changed, 302 insertions(+), 151 deletions(-) create mode 100644 scala3doc/src/dotty/dokka/SourceLinks.scala delete mode 100644 scala3doc/src/dotty/dokka/transformers/ScalaSourceLinksTransformer.scala delete mode 100644 scala3doc/src/dotty/dokka/translators/ScalaDocumentableToPageTranslator.scala create mode 100644 scala3doc/test/dotty/dokka/SourceLinksTests.scala diff --git a/project/Build.scala b/project/Build.scala index 73e3073ff1bb..44b6d19db06d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1459,8 +1459,10 @@ object Build { def asScala3doc: Project = { def generateDocumentation(targets: String, name: String, outDir: String, params: String = "") = Def.taskDyn { - val sourceMapping = "=https://github.com/lampepfl/dotty/tree/master#L" - run.in(Compile).toTask(s""" -d output/$outDir -t $targets -n "$name" -s $sourceMapping $params""") + val sourcesAndRevision = "-s github://lampepfl/dotty --revision master" + run.in(Compile).toTask( + s""" -d output/$outDir -t $targets -n "$name" $sourcesAndRevision $params""" + ) } def joinProducts(products: Seq[java.io.File]): String = diff --git a/scala3doc/src/dotty/dokka/DottyDokkaConfig.scala b/scala3doc/src/dotty/dokka/DottyDokkaConfig.scala index 4c69b057cfa8..16b47ff266c3 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaConfig.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaConfig.scala @@ -19,10 +19,11 @@ case class DottyDokkaConfig(docConfiguration: DocConfiguration) extends DokkaCon lazy val staticSiteContext = docConfiguration.args.docsRoot.map(path => StaticSiteContext(File(path).getAbsoluteFile(), Set(mkSourceSet.asInstanceOf[SourceSetWrapper]))) + lazy val sourceLinks: SourceLinks = SourceLinks.load(docConfiguration) + override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = JList() lazy val mkSourceSet: DokkaSourceSet = - val sourceLinks:Set[SourceLinkDefinitionImpl] = docConfiguration.args.sourceLinks.map(SourceLinkDefinitionImpl.Companion.parseSourceLinkDefinition(_)).toSet new DokkaSourceSetImpl( /*displayName=*/ docConfiguration.args.name, /*sourceSetID=*/ new DokkaSourceSetID(docConfiguration.args.name, "main"), @@ -36,7 +37,7 @@ case class DottyDokkaConfig(docConfiguration: DocConfiguration) extends DokkaCon /*skipEmptyPackages=*/ false, // Now all our packages are empty from dokka perspective /*skipDeprecated=*/ true, /*jdkVersion=*/ 8, - /*sourceLinks=*/ sourceLinks.asJava, + /*sourceLinks=*/ JSet(), /*perPackageOptions=*/ JList(), /*externalDocumentationLinks=*/ JSet(), /*languageVersion=*/ null, diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala index f435ebbe26ec..f1d34132416c 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala @@ -4,6 +4,7 @@ import org.jetbrains.dokka.plugability._ import org.jetbrains.dokka.transformers.sources._ import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer import org.jetbrains.dokka.transformers.pages.PageTransformer +import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.{ DokkaConfiguration$DokkaSourceSet => DokkaSourceSet } @@ -82,11 +83,16 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: val scalaDocumentableToPageTranslator = extend( _.extensionPoint(CoreExtensions.INSTANCE.getDocumentableToPageTranslator) - .fromRecipe(ctx => ScalaDocumentableToPageTranslator( - ctx.single(dokkaBase.getCommentsToContentConverter), - ctx.single(dokkaBase.getSignatureProvider), - ctx.getLogger - )) + .fromRecipe(ctx => + new DocumentableToPageTranslator { + override def invoke(module: DModule): ModulePageNode = ScalaPageCreator( + ctx.single(dokkaBase.getCommentsToContentConverter), + ctx.single(dokkaBase.getSignatureProvider), + ctx.getConfiguration.asInstanceOf[DottyDokkaConfig].sourceLinks, + ctx.getLogger + ).pageForModule(module) + } + ) .overrideExtension(dokkaBase.getDocumentableToPageTranslator) ) @@ -102,17 +108,6 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: .name("inheritanceTransformer") ) - val ourSourceLinksTransformer = extend( - _.extensionPoint(CoreExtensions.INSTANCE.getDocumentableTransformer) - .fromRecipe(ctx => ScalaSourceLinksTransformer( - ctx, - ctx.single(dokkaBase.getCommentsToContentConverter), - ctx.single(dokkaBase.getSignatureProvider), - ctx.getLogger - ) - ) - ) - val ourRenderer = extend( _.extensionPoint(CoreExtensions.INSTANCE.getRenderer) .fromRecipe(ScalaHtmlRenderer(_)) @@ -131,15 +126,6 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: .name("implicitMembersExtensionTransformer") ) - val muteDefaultSourceLinksTransformer = extend( - _.extensionPoint(CoreExtensions.INSTANCE.getPageTransformer) - .fromInstance(new PageTransformer { - override def invoke(root: RootPageNode) = root - }) - .overrideExtension(dokkaBase.getSourceLinksTransformer) - .name("muteDefaultSourceLinksTransformer") - ) - val customDocumentationProvider = extend( _.extensionPoint(dokkaBase.getHtmlPreprocessors) .fromRecipe(c => SitePagesCreator(c.siteContext)) diff --git a/scala3doc/src/dotty/dokka/Main.scala b/scala3doc/src/dotty/dokka/Main.scala index 98207a368288..5acd283d7733 100644 --- a/scala3doc/src/dotty/dokka/Main.scala +++ b/scala3doc/src/dotty/dokka/Main.scala @@ -47,6 +47,9 @@ class RawArgs: @COption(name="--syntax") protected var syntax: String = null + @COption(name="--revision") + protected var revision: String = null + def toArgs = val parsedSyntax = syntax match case null => None @@ -66,7 +69,8 @@ class RawArgs: Option(projectTitle), Option(projectLogo), parsedSyntax, - Option(sourceLinks).map(_.asScala.toList).getOrElse(List.empty) + Option(sourceLinks).map(_.asScala.toList).getOrElse(List.empty), + Option(revision) ) @@ -80,7 +84,8 @@ case class Args( projectTitle: Option[String], projectLogo: Option[String], defaultSyntax: Option[Args.CommentSyntax], - sourceLinks: List[String] + sourceLinks: List[String], + revision: Option[String] ) object Args: diff --git a/scala3doc/src/dotty/dokka/SourceLinks.scala b/scala3doc/src/dotty/dokka/SourceLinks.scala new file mode 100644 index 000000000000..06215398b75f --- /dev/null +++ b/scala3doc/src/dotty/dokka/SourceLinks.scala @@ -0,0 +1,123 @@ +package dotty.dokka + +import java.nio.file.Path +import java.nio.file.Paths +import liqp.Template +import dotty.dokka.model.api._ + +case class SourceLink(val path: Option[Path], val urlTemplate: Template) + +object SourceLink: + val SubPath = "([^=]+)=(.+)".r + val KnownProvider = raw"(\w+):\/\/([^\/]+)\/([^\/]+)".r + val BrokenKnownProvider = raw"(\w+):\/\/.+".r + + def githubTemplate(organization: String, repo: String)(revision: String) = + s"""https://github.com/$organization/$repo/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}""".stripMargin + + def gitlabTemplate(organization: String, repo: String)(revision: String) = + s"""https://gitlab.com/$organization/$repo/-/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}""" + + + private def parseLinkDefinition(s: String): Option[SourceLink] = ??? + + def parse(string: String, revision: Option[String]): Either[String, SourceLink] = + def asRawTemplate = + try Right(SourceLink(None,Template.parse(string))) catch + case e: RuntimeException => + Left(s"Failed to parse template: ${e.getMessage}") + + string match + case KnownProvider(name, organization, repo) => + def withRevision(template: String => String) = + revision.fold(Left(s"No revision provided"))(rev => Right(SourceLink(None, Template.parse(template(rev))))) + + name match + case "github" => + withRevision(githubTemplate(organization, repo)) + case "gitlab" => + withRevision(gitlabTemplate(organization, repo)) + case other => + Left(s"'$other' is not a known provider, please provide full source path template.") + + case SubPath(prefix, config) => + parse(config, revision) match + case l: Left[String, _] => l + case Right(SourceLink(Some(prefix), _)) => + Left(s"Source path $string has duplicated subpath setting (scm template can not contains '=')") + case Right(SourceLink(None, template)) => + Right(SourceLink(Some(Paths.get(prefix)), template)) + case BrokenKnownProvider("gitlab" | "github") => + Left(s"Does not match known provider syntax: `://organization/repository`") + case template => asRawTemplate + + +type Operation = "view" | "edit" + +case class SourceLinks(links: Seq[SourceLink], projectRoot: Path): + def pathTo(rawPath: Path, line: Option[Int] = None, operation: Operation = "view"): Option[String] = + def resolveRelativePath(path: Path) = + links.find(_.path.forall(p => path.startsWith(p))).map { link => + val config = java.util.HashMap[String, Object]() + val pathString = path.toString.replace('\\', '/') + config.put("path", pathString) + line.foreach(l => config.put("line", l.toString)) + config.put("operation", operation) + + link.urlTemplate.render(config) + } + + if rawPath.isAbsolute then + if rawPath.startsWith(projectRoot) then resolveRelativePath(projectRoot.relativize(rawPath)) + else None + else resolveRelativePath(rawPath) + + def pathTo(member: Member): Option[String] = + member.sources.flatMap(s => pathTo(Paths.get(s.path), Option(s.lineNumber))) + +object SourceLinks: + + val usage = + """Source links provide a mapping between file in documentation and code repositry (usual)." + + |Accepted formats: + |= + | + | + |where is one of following: + | - `github:///` (requires revision to be specified as argument for scala3doc) + | - `gitlab:///` (requires revision to be specified as argument for scala3doc) + | -