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