├── .gitignore ├── pubspec.yaml ├── LICENSE ├── README.md ├── .packages ├── test └── attributable_test.dart ├── lib └── attributable.dart └── pubspec.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .pub 2 | packages/ 3 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: attributable 2 | version: 0.1.2 3 | description: > 4 | Allows to create getters and setters for attributes 5 | on a model, as well as callbacks for them. 6 | 7 | author: Roman Snitko 8 | homepage: https://github.com/snitko/dart-attributable 9 | 10 | dev_dependencies: 11 | test: '>=0.12.0' 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) <2016> 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Attributable 2 | ============ 3 | Allows you to easily define getters/setters for attributes on your class 4 | and invoke callbacks when those attributes change. 5 | 6 | Usage 7 | ----- 8 | 9 | A simple example may look like this: 10 | 11 | import 'package:attributable'; 12 | 13 | class Button extends Object with Attributable { 14 | 15 | final List attribute_names = ['caption', 'color', 'enabled']; 16 | 17 | final Map attribute_callbacks = { 18 | 'default' : (attr_name, self) => print("Attribute $attr_name now has a new value: ${self.attributes[attr_name]}"), 19 | 'caption' : (attr_name, self) => print("Caption is now '${self.caption}'"), 20 | 'enabled' : (attr_name, self) => print("Enabled is now set to '${self.enabled}'") 21 | }; 22 | 23 | // Don't forget noSuchMethod() definition here (see below) or it won't work! 24 | 25 | } 26 | 27 | The `default` callback is invoked if no other callback for the given attribute is defined. 28 | Obviously, you may omit it, so nothing happens when your attribute changes. 29 | 30 | So now we can create instances of our class and set attributes on them: 31 | 32 | main() { 33 | 34 | var button = new Button(); 35 | 36 | button.caption = "New caption"; // => Caption is now 'New caption' 37 | button.enabled = true; // => Enabled is now set to 'true' 38 | button.color = 'green'; // => Attribute color now has a new value: green 39 | 40 | } 41 | 42 | There's also a method which allows updating many attributes all at once: 43 | 44 | button.updateAttributes({ 'caption': 'New caption', 'color': 'green'}); 45 | 46 | Needless to say, callbacks will also be invoked for the attributes listed. 47 | The `updateAttributes()` is slightly more powerful, see documenation to learn more. 48 | 49 | IMPORTANT: at this point, in order for this to work, it is not enough to simply mix this 50 | abstract class into your class, you also have to define `noSuchMethod()` callback 51 | manually in your class, so that it looks something like the following: 52 | 53 | noSuchMethod(Invocation i) { 54 | try { 55 | return prvt_noSuchGetterOrSetter(i); 56 | } on NoSuchAttributeException { 57 | super.noSuchMethod(i); 58 | } 59 | } 60 | 61 | The Invocation object gets passed into the `prvt_noSuchGetterOrSetter(i)` 62 | which then checks if any attribute with such a name are defined on the class. 63 | If not, then we return control to your class and it gets to call its original 64 | `noSuchMethod()` method, which, most likely, will generate an exception. 65 | Of course, if your class has its own `noSuchMethod()` functionality, you'd have 66 | take it into account and construct the method accordingly. 67 | 68 | See `/example/button.dart` for an example (you can run it from the terminal). 69 | -------------------------------------------------------------------------------- /.packages: -------------------------------------------------------------------------------- 1 | # Generated by pub on 2017-01-12 23:05:26.575183. 2 | analyzer:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/analyzer-0.27.2/lib/ 3 | args:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/args-0.13.4+1/lib/ 4 | async:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/async-1.11.0/lib/ 5 | barback:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/barback-0.15.2+7/lib/ 6 | boolean_selector:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.1/lib/ 7 | charcode:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/charcode-1.1.0/lib/ 8 | collection:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/collection-1.9.0/lib/ 9 | convert:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/convert-1.1.1/lib/ 10 | crypto:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/crypto-1.1.1/lib/ 11 | csslib:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/csslib-0.13.1/lib/ 12 | glob:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/glob-1.1.2/lib/ 13 | html:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/html-0.12.2+2/lib/ 14 | http:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/http-0.11.3+9/lib/ 15 | http_multi_server:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.0.1/lib/ 16 | http_parser:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/http_parser-3.0.0/lib/ 17 | logging:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/logging-0.11.3/lib/ 18 | matcher:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.0+2/lib/ 19 | mime:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/mime-0.9.3/lib/ 20 | package_config:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/package_config-0.1.3/lib/ 21 | package_resolver:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/package_resolver-1.0.1/lib/ 22 | path:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/path-1.3.9/lib/ 23 | plugin:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/plugin-0.1.0/lib/ 24 | pool:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/pool-1.2.4/lib/ 25 | pub_semver:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.2.4/lib/ 26 | shelf:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/shelf-0.6.5+2/lib/ 27 | shelf_packages_handler:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/shelf_packages_handler-1.0.0/lib/ 28 | shelf_static:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/shelf_static-0.2.3+4/lib/ 29 | shelf_web_socket:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-0.2.0/lib/ 30 | source_map_stack_trace:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-1.1.3/lib/ 31 | source_maps:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.1+1/lib/ 32 | source_span:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/source_span-1.2.2/lib/ 33 | stack_trace:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.6.5/lib/ 34 | stream_channel:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/stream_channel-1.3.1/lib/ 35 | string_scanner:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/string_scanner-0.1.4+1/lib/ 36 | test:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/test-0.12.15+3/lib/ 37 | typed_data:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/typed_data-1.1.3/lib/ 38 | utf:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/utf-0.9.0+3/lib/ 39 | watcher:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/watcher-0.9.7+2/lib/ 40 | web_socket_channel:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-1.0.3/lib/ 41 | yaml:file:///home/roman/.pub-cache/hosted/pub.dartlang.org/yaml-2.1.9/lib/ 42 | attributable:lib/ 43 | -------------------------------------------------------------------------------- /test/attributable_test.dart: -------------------------------------------------------------------------------- 1 | import "package:test/test.dart"; 2 | import "dart:mirrors"; 3 | import "../lib/attributable.dart"; 4 | 5 | class DummyClass extends Object with Attributable { 6 | 7 | List attribute_callbacks_called = []; 8 | 9 | final List attribute_names = ['caption', 'title', 'attr1', 'attr2', 'attr3']; 10 | final Map default_attribute_values = { 'attr3' : 'default_value'}; 11 | final Map attribute_callbacks = { 12 | 'default' : (attr_name, self) => self.attribute_callbacks_called.add('default'), 13 | 'caption' : (attr_name, self) => self.attribute_callbacks_called.add('caption') 14 | }; 15 | 16 | noSuchMethod(Invocation i) { 17 | var result = prvt_noSuchGetterOrSetter(i); 18 | if(result != false) 19 | return result; 20 | else 21 | super.noSuchMethod(i); 22 | } 23 | 24 | } 25 | 26 | main() { 27 | 28 | var dummy; 29 | setUp(() { 30 | dummy = new DummyClass(); 31 | }); 32 | 33 | test('returns an attribute value when a getter is called', () { 34 | dummy.caption = 'New Caption'; 35 | expect(dummy.caption, equals('New Caption')); 36 | }); 37 | 38 | test('invokes a callback whenever an attribute is changed', () { 39 | dummy.caption = 'New Caption'; 40 | expect(dummy.attribute_callbacks_called.contains('caption'), isTrue); 41 | }); 42 | 43 | test('invokes a default callback if no custom callback for the attribute is defined', () { 44 | dummy.title = 'New Title'; 45 | expect(dummy.attribute_callbacks_called.contains('default'), isTrue); 46 | }); 47 | 48 | test('keeps previous value of the attribute', () { 49 | dummy.title = 'Title 1'; 50 | dummy.title = 'Title 2'; 51 | expect(dummy._old_title, equals('Title 1')); 52 | dummy.updateAttributes({ "title" : "Title 3"}); 53 | expect(dummy._old_title, equals('Title 2')); 54 | }); 55 | 56 | test('tells if new value is different from the old one', () { 57 | dummy.title = 'Title 1'; 58 | dummy.title = 'Title 2'; 59 | expect(dummy.hasAttributeChanged('title'), isTrue); 60 | dummy.title = 'Title 2'; 61 | expect(dummy.hasAttributeChanged('title'), isFalse); 62 | }); 63 | 64 | test('updates attributes in bulk', () { 65 | var dummy = new DummyClass(); 66 | dummy.updateAttributes({ 'caption' : 'new caption', 'title': 'new title'}); 67 | expect(dummy.caption, equals('new caption')); 68 | expect(dummy.title, equals('new title')); 69 | }); 70 | 71 | test('while updating attributes in bulk, ignores those with a dot', () { 72 | var dummy = new DummyClass(); 73 | // Doesn't raise an exception! 74 | dummy.updateAttributes({ 'some_associated_object.caption' : 'new caption'}); 75 | }); 76 | 77 | test('runs callbacks on attributes after updating them in bulk', () { 78 | var dummy = new DummyClass(); 79 | dummy.updateAttributes({ 'caption' : 'new caption' }); 80 | expect(dummy.attribute_callbacks_called.contains('caption'), isTrue); 81 | }); 82 | 83 | test('doesn\'t run callbacks on attributes after updating them in bulk if closure evaluates to false', () { 84 | var dummy = new DummyClass(); 85 | dummy.updateAttributes({ 'caption' : 'new caption' }, callback: () => false); 86 | expect(dummy.attribute_callbacks_called.contains('caption'), isFalse); 87 | }); 88 | 89 | test("sets default values for attributes", () { 90 | dummy.setDefaultAttributeValues(); 91 | expect(dummy.attr3, equals("default_value")); 92 | }); 93 | 94 | test("throws error if attribute doesn't exist and someone tries to update it with updateAttributes()", () { 95 | expect(() => dummy.updateAttributes({ 'non_existent_attr' : 'new caption' }), throws); 96 | }); 97 | 98 | test("doesn't throw error if attribute doesn't exist but `ingore_non_existent: true` is passed to updateAttributes()", () { 99 | expect(() => dummy.updateAttributes({ 'non_existent_attr' : 'new caption' }, ignore_non_existent: true), returnsNormally); 100 | }); 101 | 102 | 103 | } 104 | 105 | -------------------------------------------------------------------------------- /lib/attributable.dart: -------------------------------------------------------------------------------- 1 | library attributable; 2 | import 'dart:mirrors'; 3 | 4 | class NoSuchAttributeException implements Exception { 5 | String cause; 6 | NoSuchAttributeException(this.cause); 7 | } 8 | 9 | /** 10 | * Allows you to easily define getters/setters for attributes on your class 11 | * and invoke callbacks when those attributes change. 12 | * 13 | * Please see README for explanation and code samples and ../examples/ for 14 | * an example of a class that employs attributable. 15 | */ 16 | abstract class Attributable { 17 | 18 | /// A Map of all the callbacks for the attributes that are invoked when an attribute changes. 19 | final Map attribute_callbacks = {}; 20 | 21 | /// Attributes names and values for them, stored as a map. Do not fuck with this 22 | /// property. Read from it, but don't write. 23 | final Map attributes = {}; 24 | 25 | /// Previous attribute values end up here. Useful to find out whether something has changed. 26 | final Map old_attribute_values = {}; 27 | 28 | /// This property defines which attributes get to be attributes: they will have defined 29 | /// getters and setters for them. 30 | final List attribute_names = []; 31 | 32 | /// Sometimes we want to set attributes to their default value. 33 | /// the #setDefaultAttributeValues does exactly for each attribute name and value listed 34 | /// in this property. 35 | final Map default_attribute_values = {}; 36 | 37 | /** 38 | * Invokes a callback for a given attribute. If no callback for that specific attribute is defined, 39 | * invokes a callback named `default` (if that one is defined, of course). 40 | */ 41 | invokeAttributeCallback(attr_name) { 42 | if(attribute_callbacks[attr_name] != null) { 43 | attribute_callbacks[attr_name](attr_name, reflect(this).reflectee); 44 | } else if(attribute_callbacks['default'] != null) { 45 | attribute_callbacks['default'](attr_name, reflect(this).reflectee); 46 | }; 47 | } 48 | 49 | /** 50 | * Checks whether a given attribute had a previous value different from the current one. 51 | */ 52 | hasAttributeChanged(attr_name, { ignore_null: false }) { 53 | if(!attribute_names.contains(attr_name)) 54 | throw new Exception("No attribute `$attr_name` was found in $this"); 55 | if(ignore_null) 56 | return (attributes[attr_name] != old_attribute_values[attr_name] && old_attribute_values[attr_name] != null); 57 | else 58 | return (attributes[attr_name] != old_attribute_values[attr_name]); 59 | } 60 | 61 | /** 62 | * Updates registered attributes with values provided, then run callbacks on them. 63 | * Optionally, one can provide a function to be run after the attributes 64 | * are set (`callback` attribute). If this function evalutes to false, no callbacks 65 | * would be run (useful in validations). 66 | * 67 | * If ignore_non_existent is set to true, it will not raise error while trying 68 | * to update non-existent attributes. 69 | */ 70 | updateAttributes(Map new_values, { callback: null, ignore_non_existent: false }) { 71 | 72 | if(new_values == null) 73 | return; 74 | 75 | var changed_attrs = []; 76 | 77 | new_values.forEach((k,v) { 78 | if(attribute_names.contains(k)) { 79 | prvt_setOldValue(k, attributes[k]); 80 | attributes[k] = v; 81 | changed_attrs.add(k); 82 | } else if(k.contains('.')) { /* do nothing */ 83 | } else { 84 | if(!ignore_non_existent) 85 | throw Exception('$this doesn\'t have attribute \'$k\''); 86 | } 87 | }); 88 | 89 | if(callback == false) 90 | return; 91 | else if(callback == null || callback()) { 92 | new_values.forEach((k,v) { 93 | invokeAttributeCallback(k); 94 | }); 95 | return true; 96 | } else { 97 | return false; 98 | } 99 | 100 | } 101 | 102 | void setDefaultAttributeValues() { 103 | this.default_attribute_values.forEach((k,v) { 104 | if(this.attributes[k] == null) 105 | this.attributes[k] = v; 106 | }); 107 | } 108 | 109 | /** 110 | * THIS IS A PRIVATE METHOD! 111 | * 112 | * Catches getter and setter calls for non-existent instance variables 113 | * and then uses attributes[] List to get and set values. 114 | * 115 | * This method should be called from noSuchMethod() callback in the class 116 | * in which this mixin is included. It returns false if no attribute was found. 117 | * You can later check for the return value and decide what to do next in the 118 | * noSuchMethod() callback in your class. 119 | */ 120 | prvt_noSuchGetterOrSetter(Invocation i) { 121 | 122 | var attr_name = MirrorSystem.getName(i.memberName).replaceFirst(new RegExp('='), ''); 123 | 124 | var get_me_old_value = false; 125 | if(attr_name.contains(new RegExp(r'^_old_'))) { 126 | attr_name = attr_name.replaceFirst(new RegExp(r'^_old_'), ''); 127 | get_me_old_value = true; 128 | } 129 | 130 | if(!attribute_names.contains(attr_name)) { throw new NoSuchAttributeException("No attribute `$attr_name` was found in $this"); } 131 | 132 | if(i.isSetter) { 133 | prvt_setOldValue(attr_name, attributes[attr_name]); 134 | attributes[attr_name] = i.positionalArguments[0]; 135 | invokeAttributeCallback(attr_name); 136 | return true; 137 | } else { 138 | if(get_me_old_value) 139 | return old_attribute_values[attr_name]; 140 | else 141 | return attributes[attr_name]; 142 | } 143 | } 144 | 145 | prvt_setOldValue(attr_name, value) { 146 | old_attribute_values[attr_name] = value; 147 | } 148 | 149 | 150 | } 151 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See http://pub.dartlang.org/doc/glossary.html#lockfile 3 | packages: 4 | analyzer: 5 | description: 6 | name: analyzer 7 | url: "https://pub.dartlang.org" 8 | source: hosted 9 | version: "0.27.2" 10 | args: 11 | description: 12 | name: args 13 | url: "https://pub.dartlang.org" 14 | source: hosted 15 | version: "0.13.4+1" 16 | async: 17 | description: 18 | name: async 19 | url: "https://pub.dartlang.org" 20 | source: hosted 21 | version: "1.11.0" 22 | barback: 23 | description: 24 | name: barback 25 | url: "https://pub.dartlang.org" 26 | source: hosted 27 | version: "0.15.2+7" 28 | boolean_selector: 29 | description: 30 | name: boolean_selector 31 | url: "https://pub.dartlang.org" 32 | source: hosted 33 | version: "1.0.1" 34 | charcode: 35 | description: 36 | name: charcode 37 | url: "https://pub.dartlang.org" 38 | source: hosted 39 | version: "1.1.0" 40 | collection: 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.9.0" 46 | convert: 47 | description: 48 | name: convert 49 | url: "https://pub.dartlang.org" 50 | source: hosted 51 | version: "1.1.1" 52 | crypto: 53 | description: 54 | name: crypto 55 | url: "https://pub.dartlang.org" 56 | source: hosted 57 | version: "1.1.1" 58 | csslib: 59 | description: 60 | name: csslib 61 | url: "https://pub.dartlang.org" 62 | source: hosted 63 | version: "0.13.1" 64 | glob: 65 | description: 66 | name: glob 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "1.1.2" 70 | html: 71 | description: 72 | name: html 73 | url: "https://pub.dartlang.org" 74 | source: hosted 75 | version: "0.12.2+2" 76 | http: 77 | description: 78 | name: http 79 | url: "https://pub.dartlang.org" 80 | source: hosted 81 | version: "0.11.3+9" 82 | http_multi_server: 83 | description: 84 | name: http_multi_server 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "2.0.1" 88 | http_parser: 89 | description: 90 | name: http_parser 91 | url: "https://pub.dartlang.org" 92 | source: hosted 93 | version: "3.0.0" 94 | logging: 95 | description: 96 | name: logging 97 | url: "https://pub.dartlang.org" 98 | source: hosted 99 | version: "0.11.3" 100 | matcher: 101 | description: 102 | name: matcher 103 | url: "https://pub.dartlang.org" 104 | source: hosted 105 | version: "0.12.0+2" 106 | mime: 107 | description: 108 | name: mime 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.9.3" 112 | package_config: 113 | description: 114 | name: package_config 115 | url: "https://pub.dartlang.org" 116 | source: hosted 117 | version: "0.1.3" 118 | package_resolver: 119 | description: 120 | name: package_resolver 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.0.1" 124 | path: 125 | description: 126 | name: path 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.3.9" 130 | plugin: 131 | description: 132 | name: plugin 133 | url: "https://pub.dartlang.org" 134 | source: hosted 135 | version: "0.1.0" 136 | pool: 137 | description: 138 | name: pool 139 | url: "https://pub.dartlang.org" 140 | source: hosted 141 | version: "1.2.4" 142 | pub_semver: 143 | description: 144 | name: pub_semver 145 | url: "https://pub.dartlang.org" 146 | source: hosted 147 | version: "1.2.4" 148 | shelf: 149 | description: 150 | name: shelf 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "0.6.5+2" 154 | shelf_packages_handler: 155 | description: 156 | name: shelf_packages_handler 157 | url: "https://pub.dartlang.org" 158 | source: hosted 159 | version: "1.0.0" 160 | shelf_static: 161 | description: 162 | name: shelf_static 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "0.2.3+4" 166 | shelf_web_socket: 167 | description: 168 | name: shelf_web_socket 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "0.2.0" 172 | source_map_stack_trace: 173 | description: 174 | name: source_map_stack_trace 175 | url: "https://pub.dartlang.org" 176 | source: hosted 177 | version: "1.1.3" 178 | source_maps: 179 | description: 180 | name: source_maps 181 | url: "https://pub.dartlang.org" 182 | source: hosted 183 | version: "0.10.1+1" 184 | source_span: 185 | description: 186 | name: source_span 187 | url: "https://pub.dartlang.org" 188 | source: hosted 189 | version: "1.2.2" 190 | stack_trace: 191 | description: 192 | name: stack_trace 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "1.6.5" 196 | stream_channel: 197 | description: 198 | name: stream_channel 199 | url: "https://pub.dartlang.org" 200 | source: hosted 201 | version: "1.3.1" 202 | string_scanner: 203 | description: 204 | name: string_scanner 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "0.1.4+1" 208 | test: 209 | description: 210 | name: test 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "0.12.15+3" 214 | typed_data: 215 | description: 216 | name: typed_data 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "1.1.3" 220 | utf: 221 | description: 222 | name: utf 223 | url: "https://pub.dartlang.org" 224 | source: hosted 225 | version: "0.9.0+3" 226 | watcher: 227 | description: 228 | name: watcher 229 | url: "https://pub.dartlang.org" 230 | source: hosted 231 | version: "0.9.7+2" 232 | web_socket_channel: 233 | description: 234 | name: web_socket_channel 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "1.0.3" 238 | yaml: 239 | description: 240 | name: yaml 241 | url: "https://pub.dartlang.org" 242 | source: hosted 243 | version: "2.1.9" 244 | sdks: 245 | dart: ">=1.16.0 <2.0.0" 246 | --------------------------------------------------------------------------------