applyCatalog function

String applyCatalog(
  1. String pubspec, {
  2. required Catalog catalog,
  3. required PubspecRole role,
  4. required TemplateValues values,
})

Applies catalog to the text of one pubspec and returns the new text.

Only values that differ are rewritten, so applying the catalog twice gives the same text. Every dependency the catalog manages gets the catalogMarker comment, and dependencies it no longer manages lose it.

Implementation

String applyCatalog(
  String pubspec, {
  required Catalog catalog,
  required PubspecRole role,
  required TemplateValues values,
}) {
  final usesCrlf = pubspec.contains('\r\n');
  final source = usesCrlf ? pubspec.replaceAll('\r\n', '\n') : pubspec;
  final document = loadYaml(source);
  if (document is! YamlMap) {
    return pubspec;
  }

  final editor = YamlEditor(source);
  void set(List<Object> path, Object? value) {
    final current = _valueAt(document, path);
    if (!_deepEquals(current, value)) {
      editor.update(path, value);
    }
  }

  final environment = catalog.environment;
  if (environment != null) {
    if (document['environment'] is YamlMap) {
      for (final MapEntry(:key, :value) in environment.entries) {
        set(['environment', key], value);
      }
    } else {
      set(['environment'], environment);
    }
  }

  if (role == PubspecRole.package) {
    final templated = values;
    if (catalog.version case final version?) {
      set(['version'], version);
    }
    if (catalog.publishTo case final publishTo?) {
      set(['publish_to'], publishTo);
    }
    for (final (key, value) in [
      ('homepage', catalog.homepage),
      ('repository', catalog.repository),
      ('issue_tracker', catalog.issueTracker),
      ('documentation', catalog.documentation),
    ]) {
      if (value != null) {
        set([key], templated.expand(value));
      }
    }
    if (catalog.topics case final topics?) {
      final existing = document['topics'];
      final merged = [
        if (existing is YamlList) ...existing.map((topic) => '$topic'),
      ];
      for (final topic in topics) {
        if (!merged.contains(topic)) {
          merged.add(topic);
        }
      }
      set(['topics'], merged);
    }
    if (catalog.funding case final funding?) {
      set(['funding'], [for (final url in funding) templated.expand(url)]);
    }
    if (catalog.platforms case final platforms?) {
      set(['platforms'], platforms);
    }
    if (catalog.resolution case final resolution?) {
      set(['resolution'], resolution);
    }
  }

  final dependencies = catalog.dependencies ?? const {};
  for (final section in const ['dependencies', 'dev_dependencies']) {
    final entries = document[section];
    if (entries is! YamlMap) {
      continue;
    }
    for (final name in entries.keys.whereType<String>()) {
      if (!dependencies.containsKey(name)) {
        continue;
      }
      final wanted = dependencies[name] ?? 'any';
      final current = entries[name];
      if (wanted is String &&
          current is YamlMap &&
          current.containsKey('hosted')) {
        set([section, name, 'version'], wanted);
      } else {
        set([section, name], wanted);
      }
    }
  }

  var result = markCatalogDependencies(
    editor.toString(),
    dependencies.keys.toSet(),
  );
  if (usesCrlf) {
    result = result.replaceAll('\n', '\r\n');
  }
  return result;
}