generate function

Future<List<GeneratedFile>> generate(
  1. RouteTree tree,
  2. BuildConfig config
)

Generates framework entry files from a scanned route tree.

Implementation

Future<List<GeneratedFile>> generate(RouteTree tree, BuildConfig config) async {
  final outputDir = p.join(config.rootDir, config.outputDir);
  final sourceFiles = <String>{
    for (final entry in tree.routes) entry.filePath,
    for (final entry in tree.globalMiddleware) entry.filePath,
    for (final entry in tree.scopedMiddleware) entry.filePath,
    for (final entry in tree.scopedErrors) entry.filePath,
    if (tree.fallback case final fallback?) fallback.filePath,
    if (tree.hooks case final hooks?) hooks.filePath,
  }.toList()..sort();

  final aliases = <String, String>{};
  final imports = <String>[];
  for (var i = 0; i < sourceFiles.length; i++) {
    final filePath = sourceFiles[i];
    final relative = p
        .relative(filePath, from: outputDir)
        .replaceAll('\\', '/');
    final alias = '\$i$i';
    aliases[filePath] = alias;
    imports.add("import '$relative' as $alias;");
  }

  final routeGroups = <String, List<RouteEntry>>{};
  for (final route in tree.routes) {
    routeGroups.putIfAbsent(route.path, () => <RouteEntry>[]).add(route);
  }

  final app = StringBuffer()
    ..writeln("// Generated by spry - do not edit.")
    ..writeln(
      "import 'package:spry/spry.dart' show Spry, ErrorRoute, MiddlewareRoute;",
    );

  final usesHttpMethod =
      routeGroups.values.any(
        (entries) => entries.any((it) => it.method != null),
      ) ||
      tree.globalMiddleware.any((it) => it.method != null) ||
      tree.scopedMiddleware.any((it) => it.method != null) ||
      tree.scopedErrors.any((it) => it.method != null);
  if (usesHttpMethod) {
    app.writeln("import 'package:spry/spry.dart' show HttpMethod;");
  }

  final wildcardEntries = <RouteEntry>[
    ...tree.routes,
  ];
  final fallback = tree.fallback;
  if (fallback != null) {
    wildcardEntries.add(fallback);
  }
  final needsWildcardWrapper = wildcardEntries.any(
    (entry) => entry.wildcardParam != null,
  );
  if (needsWildcardWrapper) {
    app.writeln(
      "import 'package:spry/spry.dart' show Event, Handler, RouteParams;",
    );
  }

  for (final line in imports) {
    app.writeln(line);
  }

  app
    ..writeln()
    ..writeln(
      needsWildcardWrapper
          ? '''
Handler _withWildcardParam(Handler handler, String name) {
  return (event) => handler(
    Event(
      app: event.app,
      request: event.request,
      context: event.context,
      params: RouteParams({
        ...event.params,
        if (event.params.wildcard case final wildcard?) name: wildcard,
      }),
      locals: event.locals,
    ),
  );
}
'''
          : '',
    )
    ..writeln('final app = Spry(')
    ..writeln('  routes: {');

  final sortedPaths = routeGroups.keys.toList()..sort();
  for (final path in sortedPaths) {
    final entries = routeGroups[path]!..sort(_compareRouteEntries);
    app.writeln("    '${_escape(path)}': {");
    for (final entry in entries) {
      final key = entry.method == null ? 'null' : _methodLiteral(entry.method!);
      final alias = aliases[entry.filePath]!;
      app.writeln('      $key: ${_handlerLiteral(alias, entry)},');
    }
    app.writeln('    },');
  }

  app
    ..writeln('  },')
    ..writeln('  middleware: [');

  for (final entry in [...tree.globalMiddleware, ...tree.scopedMiddleware]) {
    final alias = aliases[entry.filePath]!;
    final methodPart = entry.method == null
        ? ''
        : ', method: ${_methodLiteral(entry.method!)}';
    app.writeln(
      "    MiddlewareRoute(path: '${_escape(entry.path)}'$methodPart, handler: $alias.middleware),",
    );
  }

  app
    ..writeln('  ],')
    ..writeln('  errors: [');

  for (final entry in tree.scopedErrors) {
    final alias = aliases[entry.filePath]!;
    final methodPart = entry.method == null
        ? ''
        : ', method: ${_methodLiteral(entry.method!)}';
    app.writeln(
      "    ErrorRoute(path: '${_escape(entry.path)}'$methodPart, handler: $alias.onError),",
    );
  }

  app.writeln('  ],');

  if (tree.fallback case final fallback?) {
    final alias = aliases[fallback.filePath]!;
    app
      ..writeln('  fallback: {')
      ..writeln('    null: ${_handlerLiteral(alias, fallback)},')
      ..writeln('  },');
  }

  if (config.target != BuildTarget.cloudflare) {
    app.writeln("  publicDir: '${_escape(config.publicDir)}',");
  }

  app.writeln(');');

  final hooksBuffer = StringBuffer()
    ..writeln("// Generated by spry - do not edit.");
  if (tree.hooks case final hooks?) {
    hooksBuffer
      ..writeln(
        "import '${_relativeImport(hooks.filePath, from: outputDir)}' as \$source;",
      )
      ..writeln()
      ..writeln(
        hooks.hasOnStart
            ? 'final onStart = \$source.onStart;'
            : 'final onStart = null;',
      )
      ..writeln(
        hooks.hasOnStop
            ? 'final onStop = \$source.onStop;'
            : 'final onStop = null;',
      )
      ..writeln(
        hooks.hasOnError
            ? 'final onError = \$source.onError;'
            : 'final onError = null;',
      );
  } else {
    hooksBuffer
      ..writeln()
      ..writeln('final onStart = null;')
      ..writeln('final onStop = null;')
      ..writeln('final onError = null;');
  }
  final spec = buildTargetSpec(config);
  final main = _generateMain(spec);
  final files = <GeneratedFile>[
    GeneratedFile(path: 'app.dart', content: app.toString()),
    GeneratedFile(path: 'hooks.dart', content: hooksBuffer.toString()),
    GeneratedFile(path: 'main.dart', content: main),
  ];
  files.addAll(spec.extraFiles);
  return files;
}