Skip to content

Instantly share code, notes, and snippets.

@sma
Created May 2, 2023 09:05
Show Gist options
  • Save sma/4cacebccd84650d12b3dbbae8c78f207 to your computer and use it in GitHub Desktop.
Save sma/4cacebccd84650d12b3dbbae8c78f207 to your computer and use it in GitHub Desktop.

I wrote

/// Parses [input] as an .ini configuration.
Map<String, Map<String, String>> 

and Copilot completed this:

/// Parses [input] as an .ini configuration.
Map<String, Map<String, String>> parse(String input) {
  final result = <String, Map<String, String>>{};
  var section = '';
  for (final line in input.split('\n')) {
    if (line.startsWith('[')) {
      section = line.substring(1, line.indexOf(']'));
      result[section] = {};
    } else if (line.contains('=')) {
      final parts = line.split('=');
      result[section][parts[0].trim()] = parts[1].trim();
    }
  }
  return result;
}

That's not bad, but it's not great, either. It should probably trim the lines and ignore empty lines and line comments starting with ;. When trying it a couple of minutes before, that worked. That indexOf(']') could return -1 and will then break the parser. Also, splitting at = is wrong, because we must split only at the first occurence, something the Dart split method can't do. Furthermore, we don't detect a missing section header or garbage input.

I added a .map((line) => line.trim()) to the for and when I moved the cursor in a new line following the for statement, Copilot added:

    if (line.isEmpty || line.startsWith(';')) continue;

In the following if statement, I added && and waited for the correct completion:

    if (line.startsWith('[') && line.endsWith(']')) {

To fix the remaining problems, I had to just move the cursor to the beginning of each line and Copilot was clever enough to do the correct thing which is kind of amazing.

Here's the final result:

/// Parses [input] as an .ini configuration.
Map<String, Map<String, String>> parse(String input) {
  final result = <String, Map<String, String>>{};
  var section = '';
  for (final line in input.split('\n').map((line) => line.trim())) {
    if (line.isEmpty || line.startsWith(';')) continue;
    if (line.startsWith('[') && line.endsWith(']')) {
      result[section = section = line.substring(1, line.length - 1)] = {};
    } else if (line.contains('=')) {
      final i = line.indexOf('=');
      final key = line.substring(0, i).trim();
      final value = line.substring(i + 1).trim();
      if (section.isEmpty) {
        throw FormatException('Invalid line: $line');
      }
      result[section]![key] = value;
    } else {
      throw FormatException('Invalid line: $line');
    }
  }
  return result;
}

Does it work? Let's add tests.

I write

test('empty',

and get my first unit test:

test('empty', () {
  expect(parse(''), {});
});

By prompting test('comment' I get this:

test('comment', () {
  expect(parse('; comment\n'), {});
});

By prompting test('single section' I get this:

test('single section', () {
  expect(parse('[section]\n'), {'section': {}});
});

It then provided these tests on its own:

test('multiple sections', () {
  expect(parse('[section1]\n[section2]\n'), {'section1': {}, 'section2': {}});
});
test('single key-value', () {
  expect(parse('[section]\nkey=value\n'), {
    'section': {'key': 'value'}
  });
});
test('multiple key-values', () {
  expect(parse('[section]\nkey1=value1\nkey2=value2\n'), {
    'section': {'key1': 'value1', 'key2': 'value2'}
  });
});

Then, I prompted 'invalid section' and 'invalid key-value' and got the correct code.

test('invalid section', () {
  expect(() => parse('[section\n'), throwsFormatException);
});
test('invalid key-value', () {
  expect(() => parse('[section]\nkey\n'), throwsFormatException);
});

The only prompt, that Copilot didn't know how to answer, was 'missing section'. I had to write this final test on my own:

test('missing section', () {
  expect(() => parse('key=value\n'), throwsFormatException);
});

I got an implemention and 9 unit tests in less than 5 minutes and probably faster than searching for a package and reviewing it. This is of course a very simple example but Copilot was big time safer in this case.

I also asked for ChatGPT for unit tests and it wrote 7 tests that also completely covered the function (it didn't test for comments and provided that as 8th test upon request) in less than a minute.

/// Parses [input] as an .ini configuration.
Map<String, Map<String, String>> parse(String input) {
final result = <String, Map<String, String>>{};
var section = '';
for (final line in input.split('\n').map((line) => line.trim())) {
if (line.isEmpty || line.startsWith(';')) continue;
if (line.startsWith('[') && line.endsWith(']')) {
result[section = section = line.substring(1, line.length - 1)] = {};
} else if (line.contains('=')) {
final i = line.indexOf('=');
final key = line.substring(0, i).trimRight();
final value = line.substring(i + 1).trimLeft();
if (section.isEmpty) {
throw FormatException('Invalid line: $line');
}
result[section]?[key] = value;
} else {
throw FormatException('Invalid line: $line');
}
}
return result;
}
import 'package:test/test.dart';
void main() {
test('empty', () {
expect(parse(''), {});
expect(parse('\n\n'), {});
});
test('comment', () {
expect(parse('; comment\n'), {});
expect(parse(' ; comment\n'), {});
});
test('single section', () {
expect(parse('[section]\n'), {'section': {}});
});
test('multiple sections', () {
expect(parse('[section1]\n[section2]\n'), {'section1': {}, 'section2': {}});
});
test('single key-value', () {
expect(parse('[section]\nkey=value\n'), {
'section': {'key': 'value'}
});
});
test('multiple key-values', () {
expect(parse('[section]\nkey1=value1\nkey2=value2\n'), {
'section': {'key1': 'value1', 'key2': 'value2'}
});
});
test('invalid section', () {
expect(() => parse('[section\n'), throwsFormatException);
});
test('invalid key-value', () {
expect(() => parse('[section]\nkey\n'), throwsFormatException);
});
test('missing section', () {
expect(() => parse('key=value\n'), throwsFormatException);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment