From fc062a7dc2159a4ca682dd376a32c5773c11a9d7 Mon Sep 17 00:00:00 2001 From: Vojta Jina Date: Mon, 8 Apr 2013 18:53:34 -0700 Subject: [PATCH] Initial super lame version --- .gitignore | 3 ++ Gruntfile.coffee | 18 ++++++++++ lib/di.dart | 66 ++++++++++++++++++++++++++++++++++ lib/mirrors.dart | 4 +++ package.json | 17 +++++++++ pubspec.yaml | 8 +++++ test/fixed-unittest.dart | 33 +++++++++++++++++ test/main.dart | 78 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 227 insertions(+) create mode 100644 .gitignore create mode 100644 Gruntfile.coffee create mode 100644 lib/di.dart create mode 100644 lib/mirrors.dart create mode 100644 package.json create mode 100644 pubspec.yaml create mode 100644 test/fixed-unittest.dart create mode 100644 test/main.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eacca0d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +packages +pubspec.lock diff --git a/Gruntfile.coffee b/Gruntfile.coffee new file mode 100644 index 0000000..4307130 --- /dev/null +++ b/Gruntfile.coffee @@ -0,0 +1,18 @@ +spawn = require('child_process').spawn + +DART_VM = process.env.DART_VM or '../dart-sdk/bin/dart' + +module.exports = (grunt) -> + grunt.initConfig + watch: + tests: + files: ['test/**/*.dart', 'lib/**/*.dart'] + tasks: ['dart'] + dart: + tests: + entry: 'test/main.dart' + + grunt.registerMultiTask 'dart', 'run dart program', -> + spawn(DART_VM, [@data.entry], {stdio: 'inherit'}).on 'close', @async() + + grunt.loadNpmTasks 'grunt-contrib-watch' diff --git a/lib/di.dart b/lib/di.dart new file mode 100644 index 0000000..9e4488a --- /dev/null +++ b/lib/di.dart @@ -0,0 +1,66 @@ +import 'mirrors.dart'; +import 'dart:async'; + +class Injector { + // should be + Map providers = new Map(); + // should be + Map instances = new Map(); + + Injector([Map types]) { + if (types != null) { + // create map, because Dart is stupid and makes us deal with strings rather than types + types.forEach((key, value) { + providers[key.toString()] = value.toString(); + }); + } + } + + ClassMirror _getClassMirrorByTypeName (String typeName) { + // overriden provider + if (providers.containsKey(typeName)) { + typeName = providers[typeName]; + } + + for (var lib in currentMirrorSystem().libraries.values) { + if (lib.classes.containsKey(typeName)) { + return lib.classes[typeName]; + } + } + } + + ClassMirror _getClassMirrorFromType(Type type) { + // terrible hack because we can't get a qualified name from a Type + // dartbug.com/8041 + // dartbug.com/9395 + return _getClassMirrorByTypeName(type.toString()); + } + + + dynamic _getInstanceByTypeName(String typeName) { + if (instances.containsKey(typeName)) { + return instances[typeName]; + } + + ClassMirror cm = _getClassMirrorByTypeName(typeName); + MethodMirror ctor = cm.constructors.values.first; + + resolveArgument(p) { + return _getInstanceByTypeName(p.type.simpleName); + } + + var positionalArgs = ctor.parameters.map(resolveArgument).toList(); + var namedArgs = null; + var instance = deprecatedFutureValue(cm.newInstance(ctor.constructorName, positionalArgs, namedArgs)); + + instances[typeName] = instance; + + return instance; + } + + + // PUBLIC API + dynamic get(Type type) { + return _getInstanceByTypeName(type.toString()).reflectee; + } +} diff --git a/lib/mirrors.dart b/lib/mirrors.dart new file mode 100644 index 0000000..738691c --- /dev/null +++ b/lib/mirrors.dart @@ -0,0 +1,4 @@ +// just to get rid of the warning +library mirrors; + +export 'dart:mirrors'; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d1ba76a --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "dart-di", + "version": "0.0.0", + "description": "ERROR: No README.md file found!", + "main": "index.js", + "dependencies": {}, + "devDependencies": { + "grunt": "~0.4", + "grunt-contrib-watch": "~0.3" + }, + "scripts": { + "test": "grunt dart:test" + }, + "repository": "", + "author": "Vojta Jina ", + "license": "MIT" +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..408b267 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,8 @@ +name: di +description: A prototype of Dependency Injection framework. +version: 0.0.1 +author: "Vojta Jina " +homepage: https://github.com/dart-lang/dado + +dev_dependencies: + unittest: any diff --git a/test/fixed-unittest.dart b/test/fixed-unittest.dart new file mode 100644 index 0000000..5144da1 --- /dev/null +++ b/test/fixed-unittest.dart @@ -0,0 +1,33 @@ +library unittest; + +import 'package:unittest/unittest.dart'; +import '../lib/mirrors.dart'; + +export 'package:unittest/unittest.dart'; + +// fix the testing framework ;-) +void it(String spec, TestFunction body) => test(spec, body); +void xit(String spec, TestFunction body) {} +void iit(String spec, TestFunction body) => solo_test(spec, body); + +Matcher toEqual(expected) => equals(expected); +Matcher toBe(expected) => same(expected); +Matcher instanceOf(Type t) => new IsInstanceOfTypeMatcher(t); + + +// Welcome to Dart ;-) +class IsInstanceOfTypeMatcher extends BaseMatcher { + Type t; + + IsInstanceOfTypeMatcher(Type t) { + this.t = t; + } + + bool matches(obj, MatchState matchState) { + // we should at least compare qualifiedName, but there's no way to get it from Type + return reflect(obj).type.simpleName == t.toString(); + } + + Description describe(Description description) => + description.add('an instance of ${t.toString()}'); +} diff --git a/test/main.dart b/test/main.dart new file mode 100644 index 0000000..7757e85 --- /dev/null +++ b/test/main.dart @@ -0,0 +1,78 @@ +import 'fixed-unittest.dart'; +import 'package:di/di.dart'; + +// just some classes for testing +class Abc { + String id = 'abc-id'; + + void greet() { + print('HELLO'); + } +} + +class MockAbc implements Abc { + String id = 'mock-id'; + + void greet() { + print('MOCK HELLO'); + } +} + +class Complex { + Abc value; + + Complex(Abc val) { + value = val; + } + + dynamic getValue() { + return value; + } +} + + +// pretend, you don't see this main method +void main() { + +it('should instantiate a type', () { + var injector = new Injector(); + var instance = injector.get(Abc); + + expect(instance, instanceOf(Abc)); + expect(instance.id, toEqual('abc-id')); +}); + + +it('should resolve basic dependencies', () { + var injector = new Injector(); + var instance = injector.get(Complex); + + expect(instance, instanceOf(Complex)); + expect(instance.getValue().id, toEqual('abc-id')); +}); + + +it('should allow modules and overriding providers', () { + // module is just a Map + var module = new Map(); + module[Abc] = MockAbc; + + // injector is immutable + // you can't load more modules once it's instantiated + // (you can create a child injector) + var injector = new Injector(module); + var instance = injector.get(Abc); + + expect(instance.id, toEqual('mock-id')); +}); + + +it('should only create a single instance', () { + var injector = new Injector(); + var first = injector.get(Abc); + var second = injector.get(Abc); + + expect(first, toBe(second)); +}); + +}