Skip to content

Commit

Permalink
Merge pull request #1 from roughike/trim-margin
Browse files Browse the repository at this point in the history
v1.1.0
  • Loading branch information
roughike committed Jan 15, 2021
2 parents 668b79b + ca36ec9 commit fd3a994
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 35 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,7 @@
## 1.1.0

* Adds `trimMargin()` method that behaves like Kotlin's trimMargin.

## 1.0.0+2

* "performance improvements" - replace `List.generate(indentationLevel)` with a `StringBuffer` and a for loop. Nobody will probably notice the difference, but go ahead and enjoy the performance improvement of a couple Planck times anyway.
Expand Down
26 changes: 26 additions & 0 deletions README.md
Expand Up @@ -45,6 +45,32 @@ there

It gets rid of the common indentation while preserving the relative indentation. This is like [Kotlin's trimIndent()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/trim-indent.html) or Java 12's `align()`.

### trimMargin([String marginPrefix = '|'])

Trims the leading whitespace followed by the `marginPrefix` from each line.

For example, this:

```dart
import 'package:indent/indent.dart';
print('''
| Hello
|there
| World!
'''.trimMargin());
```

outputs this:

```
Hello
there
World!
```

Behaves just like [trimMargin in Kotlin](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/trim-margin.html).

### indent(int indentationLevel)

Indents a string with the desired indentation level while preserving relative indentation.
Expand Down
15 changes: 1 addition & 14 deletions analysis_options.yaml
@@ -1,14 +1 @@
# Defines a default set of lint rules enforced for
# projects at Google. For details and rationale,
# see https://github.com/dart-lang/pedantic#enabled-lints.
include: package:pedantic/analysis_options.yaml

# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
# Uncomment to specify additional rules.
# linter:
# rules:
# - camel_case_types

analyzer:
# exclude:
# - path/to/excluded/files/**
include: package:lint/analysis_options_package.yaml
1 change: 1 addition & 0 deletions example/web/index.html
Expand Up @@ -8,6 +8,7 @@
<button id="increase">indentBy(1)</button>
<button id="strip-extra-indent">unindent()</button>
<button id="with-ten-indentation">indent(10)</button>
<button id="trim-margin">trimMargin('|')</button>
</div>
<p>getIndentationLevel(): <strong id="indentation-level">--</strong></p>
<br/>
Expand Down
4 changes: 4 additions & 0 deletions example/web/main.dart
Expand Up @@ -24,6 +24,10 @@ void main() {
document.getElementById('with-ten-indentation').onClick.listen((_) {
text.value = text.value.indent(10);
});

document.getElementById('trim-margin').onClick.listen((_) {
text.value = text.value.trimMargin('|');
});
}

void _startMonitoringIndentationLevel(TextAreaElement text) {
Expand Down
Binary file modified indent.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 61 additions & 12 deletions lib/src/indentation.dart
@@ -1,16 +1,14 @@
import 'dart:convert';

import 'package:indent/indent.dart';

/// Change indentation in a [String] while preserving existing relative
/// indentation.
///
/// For easier usage, see [IndentedString] in string_extensions.dart.
class Indentation {
const Indentation(this._value);
final String _value;
const Indentation(this._input);
final String _input;

/// Returns the indentation level of [_value].
/// Returns the indentation level of [_input].
///
/// An indentation level is determined by finding a non-empty line with the
/// least amount of leading whitespace.
Expand All @@ -27,7 +25,7 @@ class Indentation {
return _findCommonIndentationLevel(lines);
}

/// Returns [_value] with all extra indentation stripped while preserving
/// Returns [_input] with all extra indentation stripped while preserving
/// relative indentation.
///
/// For example, the input:
Expand All @@ -45,7 +43,7 @@ class Indentation {
/// Calling [unindent] is equivalent of calling [indent] with the value of 0.
String unindent() => indent(0);

/// Returns [_value] with [indentationLevel] applied while preserving relative
/// Returns [_input] with [indentationLevel] applied while preserving relative
/// indentation.
///
/// For example, the input:
Expand Down Expand Up @@ -76,7 +74,7 @@ class Indentation {
return _indent(lines, currentIndentationLevel, indentationLevel);
}

/// Returns [_value] with indentation level changed by [howMuch].
/// Returns [_input] with indentation level changed by [howMuch].
///
/// For example, the input:
///
Expand Down Expand Up @@ -109,16 +107,67 @@ class Indentation {
);
}

/// Returns [_input], but trims leading whitespace characters followed by the
/// given [marginPrefix] from each line.
///
/// Also removes the first and last lines if they are blank, i.e. they only
/// contain whitespace characters.
///
/// For example, given that the [marginPrefix] is "|" (the default), the input:
///
/// | Hello
/// | there
/// | World
///
/// will become:
///
/// Hello
/// there
/// World
///
/// Leaves lines that don't contain [marginPrefix] untouched.
String trimMargin([String marginPrefix = '|']) {
if (_inputIsNullOrBlank()) return _input;

final lines = LineSplitter.split(_input);
final buffer = StringBuffer();
var i = -1;

for (final line in lines) {
i++;

var result = line;
final leftTrimmedLine = line.trimLeft();

if ((i == 0 || i == lines.length - 1) &&
leftTrimmedLine.trimRight().isEmpty) {
// If this is the first or the last line, and it's just whitespace, we
// want to skip it.
continue;
}

if (leftTrimmedLine.length <= line.length) {
if (leftTrimmedLine.startsWith(marginPrefix)) {
result = leftTrimmedLine.replaceFirst(marginPrefix, '');
}
}

buffer.writeln(result);
}

return buffer.toString();
}

// Turns the string into _Line classes that contain the indentation level and
// unindented contents of each line.
//
// This is to avoid having to find the indentation level two times per line:
// first time in the "find common indentation level" loop, and second time
// in the loop that applies the indentation.
Iterable<_Line> _processLines() sync* {
if (_valueIsNullOrBlank()) return;
if (_inputIsNullOrBlank()) return;

for (final line in LineSplitter.split(_value)) {
for (final line in LineSplitter.split(_input)) {
final indentationMatch = _whitespace.stringMatch(line);
final indentationLevel =
indentationMatch != null && indentationMatch.isNotEmpty
Expand All @@ -129,8 +178,8 @@ class Indentation {
}
}

bool _valueIsNullOrBlank() =>
_value == null || _value.isEmpty || _value.trim().isEmpty;
bool _inputIsNullOrBlank() =>
_input == null || _input.isEmpty || _input.trim().isEmpty;

int _findCommonIndentationLevel(Iterable<_Line> lines) {
int commonIndentationLevel;
Expand Down
24 changes: 23 additions & 1 deletion lib/src/string_extensions.dart
Expand Up @@ -2,7 +2,7 @@ import 'indentation.dart';

/// Extension methods for easier indentation of strings while preserving relative
/// indentation.
extension IndentedString on String {
extension StringIndentation on String {
/// Returns the indentation level of this string.
///
/// An indentation level is determined by finding a non-empty line with the
Expand Down Expand Up @@ -88,4 +88,26 @@ extension IndentedString on String {
/// Hello
/// World
String indentBy(int howMuch) => Indentation(this).indentBy(howMuch);

/// Returns this string, but trims leading whitespace characters followed by the
/// given [marginPrefix] from each line.
///
/// Also removes the first and last lines if they are blank, i.e. they only
/// contain whitespace characters.
///
/// For example, given that the [marginPrefix] is "|" (the default), the input:
///
/// | Hello
/// | there
/// | World
///
/// will become:
///
/// Hello
/// there
/// World
///
/// Leaves lines that don't contain [marginPrefix] untouched.
String trimMargin([String marginPrefix = '|']) =>
Indentation(this).trimMargin(marginPrefix);
}
3 changes: 2 additions & 1 deletion pubspec.yaml
@@ -1,12 +1,13 @@
name: indent
description: Change indentation in multiline Dart strings while preserving the existing relative indentation.
version: 1.0.0+2
version: 1.1.0
homepage: https://github.com/roughike/indent

environment:
sdk: '>=2.7.0 <3.0.0'

dev_dependencies:
expected_output: ^1.2.2
lint: ^1.0.0
pedantic: ^1.8.0
test: ^1.6.0
18 changes: 11 additions & 7 deletions test/_indent_test.dart
Expand Up @@ -5,19 +5,23 @@ import 'package:test/test.dart';
import 'package:indent/indent.dart';

void main() {
_runDatacase(
_runDataCase(
'indentation_level_counts',
(input) => input.getIndentationLevel(),
(output) => int.parse(output),
);

// withIndentationLevel(0) and stripExtraIndentation are the same thing
_runDatacase('with_0_indentation', (input) => input.indent(0));
_runDatacase('with_0_indentation', (input) => input.unindent());
_runDatacase('with_3_indentation', (input) => input.indent(3));
_runDataCase('with_0_indentation', (input) => input.indent(0));
_runDataCase('with_0_indentation', (input) => input.unindent());
_runDataCase('with_3_indentation', (input) => input.indent(3));

_runDatacase('increase_indentation_by_3', (input) => input.indentBy(3));
_runDatacase('decrease_indentation_by_3', (input) => input.indentBy(-3));
_runDataCase('increase_indentation_by_3', (input) => input.indentBy(3));
_runDataCase('decrease_indentation_by_3', (input) => input.indentBy(-3));

_runDataCase('trim_margin_with_pipe', (input) => input.trimMargin());
_runDataCase('trim_margin_with_pipe', (input) => input.trimMargin('|'));
_runDataCase('trim_margin_with_hashbang', (input) => input.trimMargin('#!'));

test('does not count empty line as indentation', () {
expect(
Expand All @@ -37,7 +41,7 @@ void main() {
});
}

void _runDatacase(
void _runDataCase(
String filename,
dynamic Function(String) inputTransformer, [
dynamic Function(String) outputTransformer,
Expand Down
54 changes: 54 additions & 0 deletions test/trim_margin_with_hashbang.unit
@@ -0,0 +1,54 @@
>>>
#!Tell me and I forget.
#!Teach me and I remember.
#!Involve me and I learn.
#!(Benjamin Franklin)
<<<
Tell me and I forget.
Teach me and I remember.
Involve me and I learn.
(Benjamin Franklin)
>>>
#!Tell me and I forget.
#!Teach me and I remember.
#!Involve me and I learn.
#!(Benjamin Franklin)
<<<
Tell me and I forget.
Teach me and I remember.
Involve me and I learn.
(Benjamin Franklin)
>>> preserves leading whitespace for lines not containing marginPrefix
#!Tell me and I forget.
#!Teach me and I remember.
Involve me and I learn.
#!(Benjamin Franklin)
<<<
Tell me and I forget.
Teach me and I remember.
Involve me and I learn.
(Benjamin Franklin)
>>> trims first and last lines if they are blank

#!Tell me and I forget.
#!Teach me and I remember.
#!Involve me and I learn.
#!(Benjamin Franklin)

<<<
Tell me and I forget.
Teach me and I remember.
Involve me and I learn.
(Benjamin Franklin)
>>> trims marginPrefix even without preceding whitespace

#!Tell me and I forget.
#!Teach me and I remember.
#!Involve me and I learn.
#!(Benjamin Franklin)

<<<
Tell me and I forget.
Teach me and I remember.
Involve me and I learn.
(Benjamin Franklin)

0 comments on commit fd3a994

Please sign in to comment.