diff --git a/packages/view/lib/src/engine.dart b/packages/view/lib/src/engine.dart new file mode 100644 index 0000000..cad0e42 --- /dev/null +++ b/packages/view/lib/src/engine.dart @@ -0,0 +1,13 @@ +import 'view.dart'; + +/// Abstract engine interface for view rendering +abstract class Engine { + /// Get the evaluated contents of the view + Future 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 getShared(); +} diff --git a/packages/view/lib/src/exceptions.dart b/packages/view/lib/src/exceptions.dart new file mode 100644 index 0000000..d02b18c --- /dev/null +++ b/packages/view/lib/src/exceptions.dart @@ -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); +} diff --git a/packages/view/lib/src/factory.dart b/packages/view/lib/src/factory.dart new file mode 100644 index 0000000..dc424a9 --- /dev/null +++ b/packages/view/lib/src/factory.dart @@ -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 Function(String) engineResolver; + + /// The extension to engine mappings + final Map extensions; + + /// The shared data for the factory + final Map _shared = {}; + + /// The view composers + final Map> _composers = {}; + + /// The view creators + final Map> _creators = {}; + + Factory(this.finder, this.engineResolver, [this.extensions = const {}]); + + /// Create a new view instance + Future make(String path, [Map 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? creators = _creators[view.path]; + creators?.forEach((creator) => creator(view)); + } + + /// Call the composers for a given view + void _callComposers(View view) { + List? composers = _composers[view.path]; + composers?.forEach((composer) => composer(view)); + } + + /// Normalize the given view path + String _normalizePath(String path) { + return path.replaceAll('.', '/'); + } +} diff --git a/packages/view/lib/src/finder.dart b/packages/view/lib/src/finder.dart new file mode 100644 index 0000000..e56e1f5 --- /dev/null +++ b/packages/view/lib/src/finder.dart @@ -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 paths; + + /// The file extensions to search for + final List extensions; + + /// Cache of found views + final Map _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(); + } +}