Skip to content

Add experimental diff support to new code excerpter #224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/excerpter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ they follow the Dart VM's supported syntax,
and must be wrapped in forward slashes, such as `/<regexp>/`.
If you're passing a normal string, the forward slashes are unnecessary.

### Diff parameters

**Experimental:** Output might change in future updates.

Inject instructions also support injecting the unified diff between two files.
This is supported through specifying a target with a `diff-with` argument, which
accepts a path and an optional region name just like the source file.

You can also specify a `diff-u` argument to change
the surrounding shared context of the diff.
By default, a context of 3 lines is used.

### Replacement syntax

The `replace` argument accepts one or more semicolon separated
Expand Down
115 changes: 112 additions & 3 deletions packages/excerpter/lib/src/inject.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import 'dart:io';

import 'package:collection/collection.dart';
import 'package:deviation/deviation.dart';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this package new?

Copy link
Member Author

@parlough parlough Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I made it, but it has a few issues still, so I'm keeping this support as experimental until I can iron out the package and add some tests.

import 'package:deviation/unified_diff.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;

Expand Down Expand Up @@ -162,6 +164,26 @@ final class FileUpdater {
reportError(e.error);
}

Region? diffWithRegion;
String? diffWithRegionPath;
if (instruction.diffWith
case (path: final diffWithPath, region: final diffWithRegionName)) {
final combinedDiffWithPath = path.join(
baseSourcePath,
wholeFilePathBase,
diffWithPath,
);
try {
diffWithRegion = await extractor.extractRegion(
combinedDiffWithPath,
diffWithRegionName,
);
diffWithRegionPath = diffWithPath;
} on ExtractException catch (e) {
reportError(e.error);
}
}

var plaster = (instruction.plasterTemplate ?? wholeFilePlasterTemplate)
?.replaceAll(r'$defaultPlaster', defaultPlasterContent);

Expand All @@ -173,6 +195,7 @@ final class FileUpdater {
}

var updatedLines = region.linesWithPlaster(plaster);
var updatedDiffLines = diffWithRegion?.linesWithPlaster(plaster);

final transforms = [
...instruction.transforms,
Expand All @@ -182,9 +205,38 @@ final class FileUpdater {

for (final transform in transforms) {
updatedLines = transform.transform(updatedLines);
if (updatedDiffLines != null) {
updatedDiffLines = transform.transform(updatedDiffLines);
}
}

updatedLines = updatedLines.map((line) => line.trimRight());
updatedDiffLines = updatedDiffLines?.map((line) => line.trimRight());

if (updatedDiffLines != null) {
final patch = _diffAlgorithm.compute<String>(
updatedLines.toList(growable: false),
updatedDiffLines.toList(growable: false),
);

final UnifiedDiffHeader diffHeader;
if (diffWithRegionPath != null) {
diffHeader = UnifiedDiffHeader.custom(
sourceLineContent: instruction.targetPath,
targetLineContent: diffWithRegionPath,
);
} else {
diffHeader = const UnifiedDiffHeader.simple();
}

final diff = UnifiedDiff.fromPatch(
patch,
header: diffHeader,
context: instruction.diffContext,
);

updatedLines = diff.toString().trimRight().split('\n');
}

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

const DiffAlgorithm _diffAlgorithm = DiffAlgorithm.myers();

final RegExp _instructionPattern = RegExp(
r'^\s*<\?code-excerpt\s+(?:"(?<path>\S+)(?:\s\((?<region>[^)]+)\))?\s*")?(?<args>.*?)\?>$',
);

final RegExp _diffWithPattern = RegExp(
r'^(?<path>\S+)(?:\s\((?<region>[^)]+)\))?',
);

final RegExp _instructionStart = RegExp(r'^<\?code-excerpt');

final RegExp _codeBlockStart =
Expand Down Expand Up @@ -394,6 +452,9 @@ final class _InjectInstruction extends _Instruction {
final String targetPath;
final String regionName;

final ({String path, String region})? diffWith;
final int diffContext;

final List<Transform> transforms;

final int? indentBy;
Expand All @@ -405,6 +466,8 @@ final class _InjectInstruction extends _Instruction {
required this.transforms,
this.indentBy,
this.plasterTemplate,
this.diffWith,
this.diffContext = 3,
});

factory _InjectInstruction.fromArgs({
Expand All @@ -415,6 +478,8 @@ final class _InjectInstruction extends _Instruction {
}) {
String? indentByString;
String? plasterTemplate;
String? diffWithString;
String? diffContextString;

final transforms = <Transform>[];

Expand All @@ -436,6 +501,22 @@ final class _InjectInstruction extends _Instruction {
);
}
plasterTemplate = argValue;
case 'diff-with':
if (diffWithString != null) {
reportError(
'The `diff-with` argument can only be '
'specified once per instruction.',
);
}
diffWithString = argValue;
case 'diff-u':
if (diffContextString != null) {
reportError(
'The `diff-u` argument can only be '
'specified once per instruction.',
);
}
diffContextString = argValue;
case 'skip':
transforms.add(SkipTransform(int.parse(argValue)));
case 'take':
Expand All @@ -458,17 +539,45 @@ final class _InjectInstruction extends _Instruction {
}
}

final indentBy = indentByString == null ? null : int.parse(indentByString);

if (indentBy != null && indentBy < 0) {
final indentBy =
indentByString == null ? null : int.tryParse(indentByString);
if (indentBy != null && indentBy < 1) {
reportError(
'The `indent-by` argument must be positive.',
);
}

final diffContext =
diffContextString == null ? null : int.tryParse(diffContextString);
if (diffContext != null && diffContext < 1) {
reportError(
'The `diff-u` argument must be an integer greater than 1.',
);
}

({String path, String region})? diffWith;

if (diffWithString != null) {
final pathAndRegion = _diffWithPattern.firstMatch(diffWithString);
if (pathAndRegion == null) {
reportError('Invalid syntax for `diff-with` argument.');
}

diffWith = (
path: pathAndRegion.namedGroup('path') ?? '',
region: pathAndRegion.namedGroup('region') ?? '',
);
} else if (diffContext != null) {
reportError(
'The `diff-u` argument must be specified with a `diff-with` argument.',
);
}

return _InjectInstruction(
targetPath: targetPath,
regionName: regionName,
diffWith: diffWith,
diffContext: diffContext ?? 3,
indentBy: indentBy,
plasterTemplate: plasterTemplate,
transforms: transforms,
Expand Down
1 change: 1 addition & 0 deletions packages/excerpter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ environment:
dependencies:
args: ^2.4.2
collection: ^1.18.0
deviation: ^0.0.2
file: ^7.0.0
glob: ^2.1.2
meta: ^1.14.0
Expand Down
4 changes: 2 additions & 2 deletions packages/excerpter/test/updater_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ void _defaultBehavior() {
expect(results.errors, hasLength(0));
expect(results.excerptsNeedingUpdates, equals(0));
expect(results.excerptsVisited, greaterThan(0));
expect(results.totalFilesToVisit, equals(4));
expect(results.filesVisited, equals(4));
expect(results.totalFilesToVisit, equals(5));
expect(results.filesVisited, equals(5));
expect(results.madeUpdates, isFalse);
});

Expand Down
3 changes: 3 additions & 0 deletions packages/excerpter/test_data/example/first.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
void main() {
print('Hello!');
}
4 changes: 4 additions & 0 deletions packages/excerpter/test_data/example/second.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
void main() {
print('Hello!');
print('World!');
}
12 changes: 12 additions & 0 deletions packages/excerpter/test_data/expected/diff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Diff with

<?code-excerpt "first.dart" diff-with="second.dart"?>
```diff2html
--- first.dart
+++ second.dart
@@ -1,3 +1,4 @@
void main() {
print('Hello!');
+ print('World!');
}
```
5 changes: 5 additions & 0 deletions packages/excerpter/test_data/src/diff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Diff with

<?code-excerpt "first.dart" diff-with="second.dart"?>
```diff2html
```
Loading