1+ package dotty .dokka
2+
3+ import java .nio .file .Path
4+ import java .nio .file .Paths
5+ import liqp .Template
6+ import dotty .dokka .model .api ._
7+
8+ case class SourceLink (val path : Option [Path ], val urlTemplate : Template )
9+
10+ object SourceLink :
11+ val SubPath = " ([^=]+)=(.+)" .r
12+ val KnownProvider = raw " (\w+):\/\/([^\/]+)\/([^\/]+) " .r
13+ val BrokenKnownProvider = raw " (\w+):\/\/.+ " .r
14+ val ScalaDocPatten = raw " €\{(TPL_NAME|TPL_NAME|FILE_PATH|FILE_EXT|FILE_LINE|FILE_PATH_EXT)\} " .r
15+ val SupportedScalaDocPatternReplacements = Map (
16+ " €{FILE_PATH_EXT}" -> " {{ path }}" ,
17+ " €{FILE_LINE}" -> " {{ line }}"
18+ )
19+
20+ def githubTemplate (organization : String , repo : String )(revision : String ) =
21+ s """ https://github.com/ $organization/ $repo/{{ operation | replace: "view", "blob" }}/ $revision/{{ path }}#L{{ line }} """ .stripMargin
22+
23+ def gitlabTemplate (organization : String , repo : String )(revision : String ) =
24+ s """ https://gitlab.com/ $organization/ $repo/-/{{ operation | replace: "view", "blob" }}/ $revision/{{ path }}#L{{ line }} """
25+
26+
27+ private def parseLinkDefinition (s : String ): Option [SourceLink ] = ???
28+
29+ def parse (string : String , revision : Option [String ]): Either [String , SourceLink ] =
30+ def asTemplate (template : String ) =
31+ try Right (SourceLink (None ,Template .parse(template))) catch
32+ case e : RuntimeException =>
33+ Left (s " Failed to parse template: ${e.getMessage}" )
34+
35+ string match
36+ case KnownProvider (name, organization, repo) =>
37+ def withRevision (template : String => String ) =
38+ revision.fold(Left (s " No revision provided " ))(rev => Right (SourceLink (None , Template .parse(template(rev)))))
39+
40+ name match
41+ case " github" =>
42+ withRevision(githubTemplate(organization, repo))
43+ case " gitlab" =>
44+ withRevision(gitlabTemplate(organization, repo))
45+ case other =>
46+ Left (s " ' $other' is not a known provider, please provide full source path template. " )
47+
48+ case SubPath (prefix, config) =>
49+ parse(config, revision) match
50+ case l : Left [String , _] => l
51+ case Right (SourceLink (Some (prefix), _)) =>
52+ Left (s " Source path $string has duplicated subpath setting (scm template can not contains '=') " )
53+ case Right (SourceLink (None , template)) =>
54+ Right (SourceLink (Some (Paths .get(prefix)), template))
55+ case BrokenKnownProvider (" gitlab" | " github" ) =>
56+ Left (s " Does not match known provider syntax: `<name>://organization/repository` " )
57+ case scaladocSetting if ScalaDocPatten .findFirstIn(scaladocSetting).nonEmpty =>
58+ val all = ScalaDocPatten .findAllIn(scaladocSetting)
59+ val (supported, unsupported) = all.partition(SupportedScalaDocPatternReplacements .contains)
60+ if unsupported.nonEmpty then Left (s " Unsupported patterns from scaladoc format are used: ${unsupported.mkString(" " )}" )
61+ else asTemplate(supported.foldLeft(string)((template, pattern) =>
62+ template.replace(pattern, SupportedScalaDocPatternReplacements (pattern))))
63+
64+ case template => asTemplate(template)
65+
66+
67+ type Operation = " view" | " edit"
68+
69+ case class SourceLinks (links : Seq [SourceLink ], projectRoot : Path ):
70+ def pathTo (rawPath : Path , line : Option [Int ] = None , operation : Operation = " view" ): Option [String ] =
71+ def resolveRelativePath (path : Path ) =
72+ links.find(_.path.forall(p => path.startsWith(p))).map { link =>
73+ val config = java.util.HashMap [String , Object ]()
74+ val pathString = path.toString.replace('\\ ' , '/' )
75+ config.put(" path" , pathString)
76+ line.foreach(l => config.put(" line" , l.toString))
77+ config.put(" operation" , operation)
78+
79+ link.urlTemplate.render(config)
80+ }
81+
82+ if rawPath.isAbsolute then
83+ if rawPath.startsWith(projectRoot) then resolveRelativePath(projectRoot.relativize(rawPath))
84+ else None
85+ else resolveRelativePath(rawPath)
86+
87+ def pathTo (member : Member ): Option [String ] =
88+ member.sources.flatMap(s => pathTo(Paths .get(s.path), Option (s.lineNumber)))
89+
90+ object SourceLinks :
91+
92+ val usage =
93+ """ Source links provide a mapping between file in documentation and code repositry (usual)." +
94+ |Accepted formats:
95+ |<sub-path>=<source-link>
96+ |<source-link>
97+ |
98+ |where <source-link> is one of following:
99+ | - `github://<organization>/<repository>` (requires revision to be specified as argument for scala3doc)
100+ | - `gitlab://<organization>/<repository>` (requires revision to be specified as argument for scala3doc)
101+ | - <scaladoc-template>
102+ | - <template>
103+ |
104+ |<scaladoc-template> is a format for `doc-source-url` parameter scaladoc.
105+ |NOTE: We only supports `€{FILE_PATH_EXT}` and €{FILE_LINE} patterns
106+ |
107+ |<template> is a liqid template string that can accepts follwoing arguments:
108+ | - `operation`: either "view" or "edit"
109+ | - `path`: relative path of file to provide link to
110+ | - `line`: optional parameter that specify line number within a file
111+ |
112+ |
113+ |Template can defined only by subset of sources defined by path prefix represented by `<sub-path>`""" .stripMargin
114+
115+ def load (configs : Seq [String ], revision : Option [String ], projectRoot : Path ): SourceLinks =
116+ // TODO ...
117+ val mappings = configs.map(str => str -> SourceLink .parse(str, revision))
118+
119+ val errors = mappings.collect {
120+ case (template, Left (message)) =>
121+ s " ' $template': $message"
122+ }.mkString(" \n " )
123+
124+ if errors.nonEmpty then println(
125+ s """ Following templates has invalid format:
126+ | $errors
127+ |
128+ | $usage
129+ | """ .stripMargin
130+ )
131+
132+ SourceLinks (mappings.collect {case (_, Right (link)) => link}, projectRoot)
133+
134+ def load (config : DocConfiguration ): SourceLinks =
135+ load(
136+ config.args.sourceLinks,
137+ config.args.revision,
138+ Paths .get(" " ).toAbsolutePath
139+ )
0 commit comments