Skip to content

Commit df3527d

Browse files
committedJun 24, 2024··
Add experimental diff support to new code excerpter
1 parent d50b058 commit df3527d

File tree

8 files changed

+151
-5
lines changed

8 files changed

+151
-5
lines changed
 

‎packages/excerpter/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ they follow the Dart VM's supported syntax,
138138
and must be wrapped in forward slashes, such as `/<regexp>/`.
139139
If you're passing a normal string, the forward slashes are unnecessary.
140140

141+
### Diff parameters
142+
143+
**Experimental:** Output might change in future updates.
144+
145+
Inject instructions also support injecting the unified diff between two files.
146+
This is supported through specifying a target with a `diff-with` argument, which
147+
accepts a path and an optional region name just like the source file.
148+
149+
You can also specify a `diff-u` argument to change
150+
the surrounding shared context of the diff.
151+
By default, a context of 3 lines is used.
152+
141153
### Replacement syntax
142154

143155
The `replace` argument accepts one or more semicolon separated

‎packages/excerpter/lib/src/inject.dart

+112-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import 'dart:io';
55

66
import 'package:collection/collection.dart';
7+
import 'package:deviation/deviation.dart';
8+
import 'package:deviation/unified_diff.dart';
79
import 'package:meta/meta.dart';
810
import 'package:path/path.dart' as path;
911

@@ -162,6 +164,26 @@ final class FileUpdater {
162164
reportError(e.error);
163165
}
164166

167+
Region? diffWithRegion;
168+
String? diffWithRegionPath;
169+
if (instruction.diffWith
170+
case (path: final diffWithPath, region: final diffWithRegionName)) {
171+
final combinedDiffWithPath = path.join(
172+
baseSourcePath,
173+
wholeFilePathBase,
174+
diffWithPath,
175+
);
176+
try {
177+
diffWithRegion = await extractor.extractRegion(
178+
combinedDiffWithPath,
179+
diffWithRegionName,
180+
);
181+
diffWithRegionPath = diffWithPath;
182+
} on ExtractException catch (e) {
183+
reportError(e.error);
184+
}
185+
}
186+
165187
var plaster = (instruction.plasterTemplate ?? wholeFilePlasterTemplate)
166188
?.replaceAll(r'$defaultPlaster', defaultPlasterContent);
167189

@@ -173,6 +195,7 @@ final class FileUpdater {
173195
}
174196

175197
var updatedLines = region.linesWithPlaster(plaster);
198+
var updatedDiffLines = diffWithRegion?.linesWithPlaster(plaster);
176199

177200
final transforms = [
178201
...instruction.transforms,
@@ -182,9 +205,38 @@ final class FileUpdater {
182205

183206
for (final transform in transforms) {
184207
updatedLines = transform.transform(updatedLines);
208+
if (updatedDiffLines != null) {
209+
updatedDiffLines = transform.transform(updatedDiffLines);
210+
}
185211
}
186212

187213
updatedLines = updatedLines.map((line) => line.trimRight());
214+
updatedDiffLines = updatedDiffLines?.map((line) => line.trimRight());
215+
216+
if (updatedDiffLines != null) {
217+
final patch = _diffAlgorithm.compute<String>(
218+
updatedLines.toList(growable: false),
219+
updatedDiffLines.toList(growable: false),
220+
);
221+
222+
final UnifiedDiffHeader diffHeader;
223+
if (diffWithRegionPath != null) {
224+
diffHeader = UnifiedDiffHeader.custom(
225+
sourceLineContent: instruction.targetPath,
226+
targetLineContent: diffWithRegionPath,
227+
);
228+
} else {
229+
diffHeader = const UnifiedDiffHeader.simple();
230+
}
231+
232+
final diff = UnifiedDiff.fromPatch(
233+
patch,
234+
header: diffHeader,
235+
context: instruction.diffContext,
236+
);
237+
238+
updatedLines = diff.toString().trimRight().split('\n');
239+
}
188240

189241
// Remove all shared whitespace on the left.
190242
int? sharedLeftWhitespace;
@@ -302,10 +354,16 @@ final class InjectionException implements Exception {
302354
String toString() => '$filePath:$lineNumber - $error';
303355
}
304356

357+
const DiffAlgorithm _diffAlgorithm = DiffAlgorithm.myers();
358+
305359
final RegExp _instructionPattern = RegExp(
306360
r'^\s*<\?code-excerpt\s+(?:"(?<path>\S+)(?:\s\((?<region>[^)]+)\))?\s*")?(?<args>.*?)\?>$',
307361
);
308362

363+
final RegExp _diffWithPattern = RegExp(
364+
r'^(?<path>\S+)(?:\s\((?<region>[^)]+)\))?',
365+
);
366+
309367
final RegExp _instructionStart = RegExp(r'^<\?code-excerpt');
310368

311369
final RegExp _codeBlockStart =
@@ -394,6 +452,9 @@ final class _InjectInstruction extends _Instruction {
394452
final String targetPath;
395453
final String regionName;
396454

455+
final ({String path, String region})? diffWith;
456+
final int diffContext;
457+
397458
final List<Transform> transforms;
398459

399460
final int? indentBy;
@@ -405,6 +466,8 @@ final class _InjectInstruction extends _Instruction {
405466
required this.transforms,
406467
this.indentBy,
407468
this.plasterTemplate,
469+
this.diffWith,
470+
this.diffContext = 3,
408471
});
409472

410473
factory _InjectInstruction.fromArgs({
@@ -415,6 +478,8 @@ final class _InjectInstruction extends _Instruction {
415478
}) {
416479
String? indentByString;
417480
String? plasterTemplate;
481+
String? diffWithString;
482+
String? diffContextString;
418483

419484
final transforms = <Transform>[];
420485

@@ -436,6 +501,22 @@ final class _InjectInstruction extends _Instruction {
436501
);
437502
}
438503
plasterTemplate = argValue;
504+
case 'diff-with':
505+
if (diffWithString != null) {
506+
reportError(
507+
'The `diff-with` argument can only be '
508+
'specified once per instruction.',
509+
);
510+
}
511+
diffWithString = argValue;
512+
case 'diff-u':
513+
if (diffContextString != null) {
514+
reportError(
515+
'The `diff-u` argument can only be '
516+
'specified once per instruction.',
517+
);
518+
}
519+
diffContextString = argValue;
439520
case 'skip':
440521
transforms.add(SkipTransform(int.parse(argValue)));
441522
case 'take':
@@ -458,17 +539,45 @@ final class _InjectInstruction extends _Instruction {
458539
}
459540
}
460541

461-
final indentBy = indentByString == null ? null : int.parse(indentByString);
462-
463-
if (indentBy != null && indentBy < 0) {
542+
final indentBy =
543+
indentByString == null ? null : int.tryParse(indentByString);
544+
if (indentBy != null && indentBy < 1) {
464545
reportError(
465546
'The `indent-by` argument must be positive.',
466547
);
467548
}
468549

550+
final diffContext =
551+
diffContextString == null ? null : int.tryParse(diffContextString);
552+
if (diffContext != null && diffContext < 1) {
553+
reportError(
554+
'The `diff-u` argument must be an integer greater than 1.',
555+
);
556+
}
557+
558+
({String path, String region})? diffWith;
559+
560+
if (diffWithString != null) {
561+
final pathAndRegion = _diffWithPattern.firstMatch(diffWithString);
562+
if (pathAndRegion == null) {
563+
reportError('Invalid syntax for `diff-with` argument.');
564+
}
565+
566+
diffWith = (
567+
path: pathAndRegion.namedGroup('path') ?? '',
568+
region: pathAndRegion.namedGroup('region') ?? '',
569+
);
570+
} else if (diffContext != null) {
571+
reportError(
572+
'The `diff-u` argument must be specified with a `diff-with` argument.',
573+
);
574+
}
575+
469576
return _InjectInstruction(
470577
targetPath: targetPath,
471578
regionName: regionName,
579+
diffWith: diffWith,
580+
diffContext: diffContext ?? 3,
472581
indentBy: indentBy,
473582
plasterTemplate: plasterTemplate,
474583
transforms: transforms,

‎packages/excerpter/pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ environment:
1010
dependencies:
1111
args: ^2.4.2
1212
collection: ^1.18.0
13+
deviation: ^0.0.2
1314
file: ^7.0.0
1415
glob: ^2.1.2
1516
meta: ^1.14.0

‎packages/excerpter/test/updater_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ void _defaultBehavior() {
5050
expect(results.errors, hasLength(0));
5151
expect(results.excerptsNeedingUpdates, equals(0));
5252
expect(results.excerptsVisited, greaterThan(0));
53-
expect(results.totalFilesToVisit, equals(4));
54-
expect(results.filesVisited, equals(4));
53+
expect(results.totalFilesToVisit, equals(5));
54+
expect(results.filesVisited, equals(5));
5555
expect(results.madeUpdates, isFalse);
5656
});
5757

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
void main() {
2+
print('Hello!');
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
void main() {
2+
print('Hello!');
3+
print('World!');
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Diff with
2+
3+
<?code-excerpt "first.dart" diff-with="second.dart"?>
4+
```diff2html
5+
--- first.dart
6+
+++ second.dart
7+
@@ -1,3 +1,4 @@
8+
void main() {
9+
print('Hello!');
10+
+ print('World!');
11+
}
12+
```
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Diff with
2+
3+
<?code-excerpt "first.dart" diff-with="second.dart"?>
4+
```diff2html
5+
```

0 commit comments

Comments
 (0)
Please sign in to comment.