A set of libraries providing functionality to the Bettongia projects

Features

  • package:betto_common/collections.dart:

    • Stack: a generic stack data structure
    • Range: represents a range of numbers with a step size
    • IterableChecks: extension in Iterable with matching functions
    • MappedObject provides a small interface for objects with a toMap method
  • package:betto_common/result.dart:a result type that can represent a Success or a Failure

  • package:betto_common/string.dart

    • IntlString is used to map a String to a Locale. IntlStrings lets you manage a set of strings that generally mean the same thing but in different locales.
    • StringExtension provides functions primarily to help with parsing.
    • StringList is a container for a string or a list of strings.

Getting started

The libraries in this package are self-contained and should be easily added to your codebase.

Usage

The examples directory (examples/) has a number of ready-to-try demo files.

Collection

Stack

final stack = Stack<int>();

// Push 3 numbers onto the stack
stack.push(1);
stack.push(2);
stack.push(3);

// Print the stack
print(stack.toList());

// Pop 1 number off the stack
print('Pop: ${stack.pop()}');

// Print the stack
print(stack.toList());

Result:

[1, 2, 3]
Pop: 3
[1, 2]

Range

A Range object can be used to iterate over a range of numbers.

Using the range() function can be a useful shorthand:

for (var i in range(start: 1, stop: 5)) {
    print(i);
}

Output:

1
2
3
4

Iterable checks

Use hasTheSameElements to check if two lists have the same elements:

void main() {
  List<int> list1 = [1, 2, 3, 4, 5];
  List<int> list2 = [1, 5, 4, 2, 3];
  List<int> list3 = [1, 2, 3, 5, 5];

  print(
    '$list1 and $list2 have the same elements: '
    '${list1.hasTheSameElements(list2)}',
  );

  print(
    '$list1 and $list3 have the same elements: '
    '${list1.hasTheSameElements(list3)}',
  );
}

Output:

[1, 2, 3, 4, 5] and [1, 5, 4, 2, 3] have the same elements: true
[1, 2, 3, 4, 5] and [1, 2, 3, 5, 5] have the same elements: false

Use isSubList to check if one list is a sub-list of another:

void main() {
  List<String> list1 = ['a', 'b', 'c'];
  List<String> list2 = ['a', 'b', 'c', 'd'];
  List<String> empty = [];

  print(
    '$list1 is a sublist of $list2: '
    '${list1.isSubList(list2)}',
  );

  print(
    '$list2 is a sublist of $list1: '
    '${list2.isSubList(list1)}',
  );

  // The empty list is a subset of all lists.
  print(
    '$empty is a sublist of $list1: '
    '${empty.isSubList(list1)}',
  );
}

Output:

[a, b, c] is a sublist of [a, b, c, d]: true
[a, b, c, d] is a sublist of [a, b, c]: false
[] is a sublist of [a, b, c]: true

MappedObject

Mapped objects indicate that they can be converted to a map.

You'll note in the example that MappedObject<String> is used to indicate that the map values will all be Strings.

class Person implements MappedObject<String> {
  final String name;
  final String address;

  Person(this.name, this.address);

  @override
  Map<String, String> toMap() => {'name': name, 'address': address};
}

void main() {
  final person = Person('Fred', '123 Main St.');

  print(person.toMap());
}

Output:

{name: Fred, address: 123 Main St.}

You can drop the generic type declaration and have the mapped values by dynamic:

class Person implements MappedObject {
  final String name;
  final int age;

  Person(this.name, this.age);

  @override
  Map<String, dynamic> toMap() => {
        'name': name,
        'age': age,
      };
}

void main() {
  final person = Person('Fred', 42);

  print(person.toMap());
}

Output:

{name: Fred, age: 42}

String

IntlString wraps a String and provides the Locale relevant to the String. We can then group the strings in an IntlStrings to help us select a useful variant from the set.

void main() {
  final locales = {
    'en': Locale.fromSubtags(languageCode: 'en'),
    'au': Locale.fromSubtags(languageCode: 'en', countryCode: 'AU'),
    'nz': Locale.fromSubtags(languageCode: 'en', countryCode: 'NZ'),
    'us': Locale.fromSubtags(languageCode: 'en', countryCode: 'US'),
    'de': Locale.fromSubtags(languageCode: 'de'),
    'ja': Locale.fromSubtags(languageCode: 'ja', countryCode: 'JP'),
  };

  // Note that we don't have translations for each locale
  final strings = IntlStrings([
    IntlString('hello', locale: locales['en']),
    IntlString("g'day", locale: locales['au']),
    IntlString('hallo', locale: locales['de']),
  ], defaultLocale: locales['au']);

  print('Default: ${strings.getString().value}');
  print('NZ: ${strings.getString(locale: locales['nz']).value}');
  print('DE: ${strings.getString(locale: locales['de']).value}');
  print('JA: ${strings.getString(locale: locales['ja']).value}');
}

Output:

Default: {value: g'day, locale: en-AU}
NZ: {value: hello, locale: en}
DE: {value: hallo, locale: de}
JA: {value: hello, locale: en}

The request for New Zealand (NZ) falls back to en; German is covered by de; There's nothing defined for ja-JP or ja so the default is used.

String extension

StringExtension extends String with a few handy checks and functions.

cutFirst cuts the first part of a String before the requests separator:

void main() {
  var (before, after, found) = 'Hello, world!'.cutFirst(',');
  print('Before: $before');
  print('After: $after');
  print('Found: $found');
}

Output:

Before: Hello
After:  world!
Found: true

Testing

To run the full test suite, generate docs etc:

make

Linux container

The Containerfile can be used to test the package on Linux.

podman build -t betto-common-cicd .
podman run --rm betto-common-cicd

Additional information

None really - it's just a portmanteau of utility items.

Libraries

collections
Collection-oriented classes and extensions
string
A small library of String-related goodies.