feat: Implement core view package components

This commit is contained in:
Patrick Stewart (aider) 2024-10-26 21:31:26 -07:00
parent f22dd8c495
commit 4b46d00dd4
4 changed files with 165 additions and 0 deletions

View file

@ -0,0 +1,13 @@
import 'view.dart';
/// Abstract engine interface for view rendering
abstract class Engine {
/// Get the evaluated contents of the view
Future<String> get(View view);
/// Add a piece of shared data to the engine
void share(String key, dynamic value);
/// Get all of the shared data for the engine
Map<String, dynamic> getShared();
}

View file

@ -0,0 +1,18 @@
/// Base exception for view related errors
class ViewException implements Exception {
final String message;
ViewException(this.message);
@override
String toString() => 'ViewException: $message';
}
/// Exception thrown when a view is not found
class ViewNotFoundException extends ViewException {
ViewNotFoundException(String message) : super(message);
}
/// Exception thrown when there's an error compiling the view
class ViewCompileException extends ViewException {
ViewCompileException(String message) : super(message);
}

View file

@ -0,0 +1,82 @@
import 'dart:async';
import 'package:path/path.dart' as path;
import 'engine.dart';
import 'finder.dart';
import 'view.dart';
import 'exceptions.dart';
/// The view factory implementation
class Factory {
/// The view finder implementation
final ViewFinder finder;
/// The engine resolver callback
final FutureOr<Engine> Function(String) engineResolver;
/// The extension to engine mappings
final Map<String, String> extensions;
/// The shared data for the factory
final Map<String, dynamic> _shared = {};
/// The view composers
final Map<String, List<Function(View)>> _composers = {};
/// The view creators
final Map<String, List<Function(View)>> _creators = {};
Factory(this.finder, this.engineResolver, [this.extensions = const {}]);
/// Create a new view instance
Future<View> make(String path, [Map<String, dynamic> data = const {}]) async {
String normalizedPath = _normalizePath(path);
// Determine the engine for this view
String extension = path.split('.').last;
String engineName = extensions[extension] ?? 'file';
Engine engine = await engineResolver(engineName);
// Create the view instance
View view = View(this, engine, normalizedPath, {..._shared, ...data});
// Call creators
_callCreators(view);
// Call composers
_callComposers(view);
return view;
}
/// Add a piece of shared data to the factory
void share(String key, dynamic value) {
_shared[key] = value;
}
/// Register a view composer
void composer(String path, Function(View) callback) {
_composers.putIfAbsent(path, () => []).add(callback);
}
/// Register a view creator
void creator(String path, Function(View) callback) {
_creators.putIfAbsent(path, () => []).add(callback);
}
/// Call the creators for a given view
void _callCreators(View view) {
List<Function(View)>? creators = _creators[view.path];
creators?.forEach((creator) => creator(view));
}
/// Call the composers for a given view
void _callComposers(View view) {
List<Function(View)>? composers = _composers[view.path];
composers?.forEach((composer) => composer(view));
}
/// Normalize the given view path
String _normalizePath(String path) {
return path.replaceAll('.', '/');
}
}

View file

@ -0,0 +1,52 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import 'exceptions.dart';
/// The view finder implementation
class ViewFinder {
/// The paths to search for views
final List<String> paths;
/// The file extensions to search for
final List<String> extensions;
/// Cache of found views
final Map<String, String> _cache = {};
ViewFinder(this.paths, [this.extensions = const ['.blade.php', '.php', '.html']]);
/// Find the given view in the filesystem
String find(String name) {
if (_cache.containsKey(name)) {
return _cache[name]!;
}
String result = _findInPaths(name);
_cache[name] = result;
return result;
}
/// Find the given view in the filesystem paths
String _findInPaths(String name) {
for (String location in paths) {
for (String extension in extensions) {
String viewPath = path.join(location, '$name$extension');
if (File(viewPath).existsSync()) {
return viewPath;
}
}
}
throw ViewNotFoundException('View [$name] not found.');
}
/// Add a location to the finder
void addLocation(String location) {
paths.add(location);
}
/// Flush the cache of located views
void flush() {
_cache.clear();
}
}