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