Skip to content

Commit 5a4308d

Browse files
authored
Add manually-specified source code links to code (#1913)
* Link to source now works, minus icons * Add material icons * Refactor dartdoc_test to avoid asynchronous delete collisions * dartfmt * Fix windows * dartfmt * Tweak padding and change icon to description * Review comments * Add example and use it in the dartdoc package. * dartfmt
1 parent 6fac6f7 commit 5a4308d

26 files changed

+403
-52
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,19 @@ Unrecognized options will be ignored. Supported options:
163163
No branch is considered to be "stable".
164164
* `%n%`: The name of this package, as defined in pubspec.yaml.
165165
* `%v%`: The version of this package as defined in pubspec.yaml.
166+
* **linkToSource**: Generate links to a source code repository based on given templates and
167+
revision information.
168+
* **excludes**: A list of directories to exclude from processing source links.
169+
* **root**: The directory to consider the 'root' for inserting relative paths into the template.
170+
Source code outside the root directory will not be linked.
171+
* **uriTemplate**: A template to substitute revision and file path information. If revision
172+
is present in the template but not specified, or if root is not specified, dartdoc will
173+
throw an exception. To hard-code a revision, don't specify it with `%r%`.
174+
175+
The following strings will be substituted in to complete the URL:
176+
* `%f%`: Relative path of file to the repository root
177+
* `%r%`: Revision
178+
* `%l%`: Line number
166179
* **warnings**: Specify otherwise ignored or set-to-error warnings to simply warn. See the lists
167180
of valid warnings in the command line help for `--errors`, `--warnings`, and `--ignore`.
168181

@@ -391,6 +404,35 @@ other elements on the page, since those may change in future versions of Dartdoc
391404
If `--auto-include-dependencies` flag is provided, dartdoc tries to automatically add
392405
all the used libraries, even from other packages, to the list of the documented libraries.
393406

407+
### Using link-to-source
408+
409+
The source linking feature in dartdoc is a little tricky to use, since pub packages do not actually
410+
include enough information to link back to source code and that's the context in which documentation
411+
is generated for the pub site. This means that for now, it must be manually specified in
412+
dartdoc_options.yaml what revision to use. It is currently recommended practice to
413+
specify a revision in dartdoc_options.yaml that points to the same revision as your public package.
414+
If you're using a documentation staging system outside of Dart's pub site, override the template and
415+
revision on the command line with the head revision number. You can use the branch name,
416+
but generated docs will generate locations that may start drifting with further changes to the branch.
417+
418+
Example dartdoc_options.yaml:
419+
```yaml
420+
link-to-source:
421+
root: '.'
422+
uriTemplate: 'https://github.com/dart-lang/dartdoc/blob/v0.28.0/%f%#L%l%'
423+
```
424+
425+
Example staging command line:
426+
```bash
427+
pub global run dartdoc --link-to-source-root '.' --link-to-source-revision 6fac6f770d271312c88e8ae881861702a9a605be --link-to-source-uri-template 'https://github.com/dart-lang/dartdoc/blob/%r%/%f#L%l%'
428+
```
429+
430+
This gets more complicated with `--auto-include-dependencies` as these command line flags
431+
will override all settings from individual packages. In that case, to preserve
432+
source links from third party packages it may be necessary to generate
433+
dartdoc_options.yaml options for each package you are intending to add source links
434+
to yourself.
435+
394436
## Issues and bugs
395437

396438
Please file reports on the [GitHub Issue Tracker][]. Issues are labeled with

dartdoc_options.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dartdoc:
2+
linkToSource:
3+
root: '.'
4+
uriTemplate: 'https://github.com/dart-lang/dartdoc/blob/v0.28.0/%f%#L%l%'

lib/resources/styles.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,29 @@ h1 .category {
437437
vertical-align: middle;
438438
}
439439

440+
.source-link {
441+
padding: 18px 4px;
442+
font-size: 12px;
443+
vertical-align: middle;
444+
}
445+
446+
@media (max-width: 768px) {
447+
.source-link {
448+
padding: 7px 2px;
449+
font-size: 10px;
450+
}
451+
}
452+
453+
#external-links {
454+
float: right;
455+
}
456+
457+
.btn-group {
458+
position: relative;
459+
display: inline-flex;
460+
vertical-align: middle;
461+
}
462+
440463
p.firstline {
441464
font-weight: bold;
442465
}

lib/src/dartdoc_options.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'package:args/args.dart';
2222
import 'package:dartdoc/dartdoc.dart';
2323
import 'package:dartdoc/src/experiment_options.dart';
2424
import 'package:dartdoc/src/io_utils.dart';
25+
import 'package:dartdoc/src/source_linker.dart';
2526
import 'package:dartdoc/src/tool_runner.dart';
2627
import 'package:dartdoc/src/tuple.dart';
2728
import 'package:dartdoc/src/warnings.dart';
@@ -1272,7 +1273,10 @@ abstract class DartdocOptionContextBase {
12721273
/// a single [ModelElement], [Package], [Category] and so forth has a single context
12731274
/// and so this can be made a member variable of those structures.
12741275
class DartdocOptionContext extends DartdocOptionContextBase
1275-
with DartdocExperimentOptionContext, PackageWarningOptionContext {
1276+
with
1277+
DartdocExperimentOptionContext,
1278+
PackageWarningOptionContext,
1279+
SourceLinkerOptionContext {
12761280
@override
12771281
final DartdocOptionSet optionSet;
12781282
@override
@@ -1573,5 +1577,6 @@ Future<List<DartdocOption>> createDartdocOptions() async {
15731577
// each DartdocOptionContext that traverses the inheritance tree itself.
15741578
]
15751579
..addAll(await createExperimentOptions())
1576-
..addAll(await createPackageWarningOptions());
1580+
..addAll(await createPackageWarningOptions())
1581+
..addAll(await createSourceLinkerOptions());
15771582
}

lib/src/html/templates.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const _partials = const <String>[
3030
'sidebar_for_category',
3131
'sidebar_for_enum',
3232
'source_code',
33+
'source_link',
3334
'sidebar_for_library',
3435
'accessor_getter',
3536
'accessor_setter',

lib/src/model.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import 'package:dartdoc/src/logging.dart';
5656
import 'package:dartdoc/src/markdown_processor.dart' show Documentation;
5757
import 'package:dartdoc/src/model_utils.dart';
5858
import 'package:dartdoc/src/package_meta.dart' show PackageMeta, FileContents;
59+
import 'package:dartdoc/src/source_linker.dart';
5960
import 'package:dartdoc/src/special_elements.dart';
6061
import 'package:dartdoc/src/tuple.dart';
6162
import 'package:dartdoc/src/utils.dart';
@@ -3215,6 +3216,13 @@ abstract class ModelElement extends Canonicalization
32153216
return _documentationFrom;
32163217
}
32173218

3219+
bool get hasSourceHref => sourceHref.isNotEmpty;
3220+
String _sourceHref;
3221+
String get sourceHref {
3222+
_sourceHref ??= new SourceLinker.fromElement(this).href();
3223+
return _sourceHref;
3224+
}
3225+
32183226
/// Returns the ModelElement(s) from which we will get documentation.
32193227
/// Can be more than one if this is a Field composing documentation from
32203228
/// multiple Accessors.

lib/src/source_linker.dart

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// A library for getting external source code links for Dartdoc.
6+
library dartdoc.source_linker;
7+
8+
import 'package:dartdoc/src/dartdoc_options.dart';
9+
import 'package:dartdoc/src/model.dart';
10+
import 'package:path/path.dart' as pathLib;
11+
12+
final uriTemplateRegexp = new RegExp(r'(%[frl]%)');
13+
14+
abstract class SourceLinkerOptionContext implements DartdocOptionContextBase {
15+
List<String> get linkToSourceExcludes =>
16+
optionSet['linkToSource']['excludes'].valueAt(context);
17+
String get linkToSourceRevision =>
18+
optionSet['linkToSource']['revision'].valueAt(context);
19+
String get linkToSourceRoot =>
20+
optionSet['linkToSource']['root'].valueAt(context);
21+
String get linkToSourceUriTemplate =>
22+
optionSet['linkToSource']['uriTemplate'].valueAt(context);
23+
}
24+
25+
Future<List<DartdocOption>> createSourceLinkerOptions() async {
26+
return <DartdocOption>[
27+
new DartdocOptionSet('linkToSource')
28+
..addAll([
29+
new DartdocOptionArgFile<List<String>>('excludes', [],
30+
isDir: true,
31+
help:
32+
'A list of directories to exclude from linking to a source code repository.'),
33+
// TODO(jcollins-g): Use [DartdocOptionArgSynth], possibly in combination with a repository type and the root directory, and get revision number automatically
34+
new DartdocOptionArgOnly<String>('revision', null,
35+
help: 'Revision number to insert into the URI.'),
36+
new DartdocOptionArgFile<String>('root', null,
37+
isDir: true,
38+
help:
39+
'Path to a local directory that is the root of the repository we link to. All source code files under this directory will be linked.'),
40+
new DartdocOptionArgFile<String>('uriTemplate', null,
41+
help:
42+
'''Substitute into this template to generate a uri for an element's source code.
43+
Dartdoc dynamically substitutes the following fields into the template:
44+
%f%: Relative path of file to the repository root
45+
%r%: Revision number
46+
%l%: Line number'''),
47+
])
48+
];
49+
}
50+
51+
class SourceLinker {
52+
final List<String> excludes;
53+
final int lineNumber;
54+
final String sourceFileName;
55+
final String revision;
56+
final String root;
57+
final String uriTemplate;
58+
59+
/// Most users of this class should use the [SourceLinker.fromElement] factory
60+
/// instead. This constructor is public for testing.
61+
SourceLinker(
62+
{List<String> this.excludes,
63+
int this.lineNumber,
64+
String this.sourceFileName,
65+
String this.revision,
66+
String this.root,
67+
String this.uriTemplate}) {
68+
assert(excludes != null, 'linkToSource excludes can not be null');
69+
if (revision != null || root != null || uriTemplate != null) {
70+
if (root == null || uriTemplate == null) {
71+
throw DartdocOptionError(
72+
'linkToSource root and uriTemplate must both be specified to generate repository links');
73+
}
74+
if (uriTemplate.contains('%r%') && revision == null) {
75+
throw DartdocOptionError(
76+
r'%r% specified in uriTemplate, but no revision available');
77+
}
78+
}
79+
}
80+
81+
/// Build a SourceLinker from a ModelElement.
82+
factory SourceLinker.fromElement(ModelElement element) {
83+
SourceLinkerOptionContext config = element.config;
84+
return new SourceLinker(
85+
excludes: config.linkToSourceExcludes,
86+
// TODO(jcollins-g): disallow defaulting? Some elements come back without
87+
// a line number right now.
88+
lineNumber: element.lineAndColumn?.item1 ?? 1,
89+
sourceFileName: element.sourceFileName,
90+
revision: config.linkToSourceRevision,
91+
root: config.linkToSourceRoot,
92+
uriTemplate: config.linkToSourceUriTemplate,
93+
);
94+
}
95+
96+
String href() {
97+
if (sourceFileName == null || root == null || uriTemplate == null)
98+
return '';
99+
if (!pathLib.isWithin(root, sourceFileName) ||
100+
excludes
101+
.any((String exclude) => pathLib.isWithin(exclude, sourceFileName)))
102+
return '';
103+
return uriTemplate.replaceAllMapped(uriTemplateRegexp, (match) {
104+
switch (match[1]) {
105+
case '%f%':
106+
var urlContext = new pathLib.Context(style: pathLib.Style.url);
107+
return urlContext.joinAll(
108+
pathLib.split(pathLib.relative(sourceFileName, from: root)));
109+
break;
110+
case '%r%':
111+
return revision;
112+
break;
113+
case '%l%':
114+
return lineNumber.toString();
115+
break;
116+
}
117+
});
118+
}
119+
}

lib/src/warnings.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,10 @@ abstract class Warnable implements Canonicalization {
208208

209209
/// Something that can be located for warning purposes.
210210
abstract class Locatable {
211+
List<Locatable> get documentationFrom;
211212
String get fullyQualifiedName;
212213
String get href;
213214

214-
List<Locatable> get documentationFrom;
215-
216215
/// A string indicating the URI of this Locatable, usually derived from
217216
/// [Element.location].
218217
String get location;

lib/templates/_head.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
{{/htmlBase}}
1919

2020
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500,400i,400,300|Source+Sans+Pro:400,300,700" rel="stylesheet">
21+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
2122
<link rel="stylesheet" href="static-assets/github.css">
2223
<link rel="stylesheet" href="static-assets/styles.css">
2324
<link rel="icon" href="static-assets/favicon.png">

lib/templates/_source_link.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{{#hasSourceHref}}
2+
<div id="external-links" class="btn-group"><a title="View source code" class="source-link" href="{{{sourceHref}}}"><i class="material-icons">description</i></a></div>
3+
{{/hasSourceHref}}

lib/templates/class.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
1010
{{#self}}
11-
<h1>{{{nameWithGenerics}}} {{kind}} {{>categorization}}</h1>
11+
<div>{{>source_link}}<h1>{{{nameWithGenerics}}} {{kind}} {{>categorization}}</h1></div>
1212
{{/self}}
1313

1414
{{#clazz}}

lib/templates/constant.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
77
</div><!--/.sidebar-offcanvas-left-->
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
10-
<h1>{{self.name}} {{self.kind}}</h1>
10+
{{#self}}
11+
<div>{{>source_link}}<h1>{{{name}}} {{kind}}</h1></div>
12+
{{/self}}
1113

1214
<section class="multi-line-signature">
1315
{{#property}}

lib/templates/constructor.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
77
</div><!--/.sidebar-offcanvas-left-->
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
10-
<h1>{{{self.nameWithGenerics}}} {{self.kind}}</h1>
10+
{{#self}}
11+
<div>{{>source_link}}<h1>{{{nameWithGenerics}}} {{kind}}</h1></div>
12+
{{/self}}
1113

1214
{{#constructor}}
1315
<section class="multi-line-signature">

lib/templates/enum.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
1010
{{#self}}
11-
<h1>{{{name}}} {{kind}} {{>categorization}}</h1>
11+
<div>{{>source_link}}<h1>{{{name}}} {{kind}} {{>categorization}}</h1></div>
1212
{{/self}}
1313

1414
{{#eNum}}

lib/templates/function.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
1010
{{#self}}
11-
<h1>{{{nameWithGenerics}}} {{kind}} {{>categorization}}</h1>
11+
<div>{{>source_link}}<h1>{{{nameWithGenerics}}} {{kind}} {{>categorization}}</h1></div>
1212
{{/self}}
1313

1414
{{#function}}

lib/templates/library.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ <h5><span class="package-name">{{parent.name}}</span> <span class="package-kind"
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
1010
{{#self}}
11-
<h1>{{{name}}} {{kind}} {{>categorization}}</h1>
11+
<div>{{>source_link}}<h1>{{{name}}} {{kind}} {{>categorization}}</h1></div>
1212
{{/self}}
1313

1414
{{#library}}

lib/templates/method.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
77
</div><!--/.sidebar-offcanvas-->
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
10-
<h1>{{{self.nameWithGenerics}}} {{self.kind}}</h1>
10+
{{#self}}
11+
<div>{{>source_link}}<h1>{{{nameWithGenerics}}} {{kind}}</h1></div>
12+
{{/self}}
1113

1214
{{#method}}
1315
<section class="multi-line-signature">

lib/templates/mixin.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
1010
{{#self}}
11-
<h1>{{{nameWithGenerics}}} {{kind}} {{>categorization}}</h1>
11+
<div>{{>source_link}}<h1>{{{nameWithGenerics}}} {{kind}} {{>categorization}}</h1></div>
1212
{{/self}}
1313

1414
{{#mixin}}

lib/templates/property.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
77
</div><!--/.sidebar-offcanvas-->
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
10-
<h1>{{self.name}} {{self.kind}}</h1>
10+
{{#self}}
11+
<div>{{>source_link}}<h1>{{name}} {{kind}}</h1></div>
12+
{{/self}}
1113

1214
{{#self}}
1315
{{#hasNoGetterSetter}}

lib/templates/top_level_constant.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
88

99
<div class="col-xs-12 col-sm-9 col-md-8 main-content">
1010
{{#self}}
11-
<h1>{{{name}}} {{kind}} {{>categorization}}</h1>
11+
<div>{{>source_link}}<h1>{{{name}}} {{kind}} {{>categorization}}</h1></div>
1212

1313
<section class="multi-line-signature">
1414
{{>name_summary}}

0 commit comments

Comments
 (0)