Send to Kindle

Tuesday, November 25, 2014

Better Polymer.dart Tests with Futures


I lamented Polymer.dart's JavaScript-y callback nature yesterday. But I didn't do much about it. Ultimately I cannot do much until the library matures into more of a Dart library as the Patterns in Polymer needs to track the library, not my weakly held strong opinions (and I do admit that it makes sense to track the JavaScript Polymer for now).

That said, I do think it worth exploring Dart futures as a means for cleaning up my tests.

Last night's test looks like:
    group('syncing <input> values', (){
      var input;

      setUp((){
        input = _container.append(createElement('<input>'));
        syncInputFromAttr(input, _el, 'state');
        _el.model.firstHalfToppings.add('pepperoni');
      });

      test('updates the input', (){
        _el.async(expectAsync((_){
          expect(
            input.value,
            startsWith('First Half: [pepperoni]')
          );
        }));
      });
    });
For a small test, that is not too horrible. Problems would arise as the complexity of the Polymer element grows.

The main problem with the above test is in the actual test:
      test('updates the input', (){
        _el.async(expectAsync((_){
          expect(
            input.value,
            startsWith('First Half: [pepperoni]')
          );
        }));
      });

The async nature of this particular test is not integral to the functionality. It is merely a necessary evil to ensure that the Polymer element has been updated. Instead, I would much prefer to write:
      test('updates the input', (){
        expect(
          input.value,
          startsWith('First Half: [pepperoni]')
        );
      });
The intent of that test is much clearer.

To make that work, I need a setup that blocks. I had forgotten that vanilla unittest in Dart support this by returning a Future:
      setUp((){
        input = _container.append(createElement('<input>'));
        syncInputFromAttr(input, _el, 'state');
        _el.model.firstHalfToppings.add('pepperoni');

       var completer = new Completer();
       _el.async(completer.complete);
       return completer.future;
      });




Day #5

Monday, November 24, 2014

Testing Polymer.dart Synchronization with Form Input Fields


Surprisingly, none of my Dart tests are failing. Or have failed. It's not that I expect Dart tests to fail with any regularity—far from it. It is just that the tests for the JavaScript version of Patterns in Polymer seem to fail every other week or so.

It probably helps that Polymer.dart has remained at 0.15 for a while now. I could also use some better tests. And since I went to all that trouble last night to write a test describing the JavaScript code from the chapter on synchronizing the plain-old form <input> value with Polymer, it seems only fair that I write the same thing for Dart.

The JavaScript test wound up looking like:
  describe('syncing <input> values', function(){
    var input;

    beforeEach(function(done){
      input = document.createElement('input');
      container.appendChild(input);

      syncInputFromAttr(input, el, 'state');

      el.model.firstHalfToppings.push('pepperoni');
      el.async(done);
    });

    it('updates the input', function(){
      expect(input.value).toEqual('pepperoni');
    });
  });
Cerate an <input>, sync it with the <x-pizza> Polymer element, add a pepperoni to the Polymer element and expect that the <input> now contains pepperoni as well.

As with last night's JavaScript solution, I am using MutationObserver objects to synchronize my elements in Dart:
syncInputFromAttr(input, el, attr) {
   var observer = new MutationObserver((changes, _) {
    changes.forEach((change) {
      if (change.attributeName == attr) {
        input.value = change.target.attributes[change.attributeName];
      }
    });
  });
  observer.observe(el, attributes: true);
}
This creates an observer with the supplied callback that is invoked when changes are seen. It then observes the supplied el, which is my <x-pizza> Polymer element in this case. Not a particularly Darty looking thing, but the callback updates the input when the desired changes are seen.

It works in my smoke tests, but I still need a repeatable unit test. Well, maybe not need, but if this approach failed in JavaScript, then it can also fail in Dart at some point. So it would be good to have such a test.

The test setup is the same as with the JavaScript test—create an <input> which is added to the container from previous setup. Then synchronize the Polymer and input elements. Lastly, add pepperoni as the first half topping:
  group('syncing <input> values', (){
    var input;

    setUp((){
      input = createElement('<input>');
      _container.append(input);

      syncInputFromAttr(input, _el, 'state');

      _el.model.firstHalfToppings.add('pepperoni');
    });

    test('updates the input', (){
      // Test will go here...
    });
  });
The test then needs to check that the input value is now set to indicate that the first half of the pizza includes pepperoni like the Polymer element:
  group('syncing <input> values', (){
    var input;

    setUp((){ /* ... /* });

    test('updates the input', (){
      expect(
        input.value,
        startsWith('First Half: [pepperoni]')
      );
    });
  });
Except that does not work:
FAIL: <x-pizza> syncing <input> values updates the input
  Expected: a string starting with 'First Half: [pepperoni]'
    Actual: ''
Fortunately, I have gotten to the point in my Polymer testing (JavaScript and Dart) at which I know that I need to wait for Polymer to update all internal and external values before testing. Furthermore, I know that I can supply a callback to Polymer's async() method such that Polymer will invoke this callback when it has updated bound variables and observables alike.

To adapt this into vanilla Dart unittest, I need to wrap the whole thing in an expectAsync() call:
group('syncing <input> values', (){
    var input;

    setUp((){ /* ... /* });

    test('updates the input', (){
      _el.async(expectAsync((_){
        expect(
          input.value,
          startsWith('First Half: [pepperoni]')
        );
      }));
    });
  });
And that actually works. I now have my very basic test working and a test of the actual functionality from this chapter both passing:
PASS: <x-pizza> has a shadowRoot
PASS: <x-pizza> syncing <input> values updates the input
All 2 tests passed.
I must admit that I begin to dislike the callback love in Polymer.dart. Both the MutationObserver and the async() methods require callbacks, making this feel very much like JavaScript code. I can only hope that this changes at some point so that I can use a Future or two. In the meantime, I will likely convert this test into a scheduled_test test. I may have to build my own Completer to make it work, but the end result ought to be a bit easier on the eye.



Day #4

Sunday, November 23, 2014

Replacing MutationObserver with Object.observe() in Polymer


I was able to fix and test a change observer for Polymer
elements. Tonight, I am curious if the Object.observe() approach might offer any benefits over the current MutationObserver approach that I have been using in Patterns in Polymer.

Given a Polymer element (el), a form input object (input), and an attribute to watch on the Polymer element, I can ensure that the value of the form element synchronizes with the Polymer element thusly:
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.attributeName == attr) {
        input.value = mutation.target[attr];
      }
    });
  });
  observer.observe(el, {attributes: true});
The observer watches el (including attribute changes). Each time changes are seen, the callback loops through the changes (mutations) looking for one that matches the attribute attr. Finding such a change, the callback then updates the form input field with the same value as the Polymer element.

This solution works just fine, but is a little cumbersome if only because MutationObserver is a lot to type.

So what about Object.observer()? It turns out that very little need change to support a switch to this new approach:
  Object.observe(el, function(changes){
    changes.forEach(function(change){
      if (change.name == attr) {
        input.value = change.object[attr];
      }
    });
  });
In fact, aside from less haughty "changes" in place of "mutations," almost nothing has changed. I still loop through the list of changes and conditionally update input. The only real difference is that I no longer have to create a mutation object that can then observe the element—the observe() code is baked right into Object.

It would be nice to filter out only those changes in which I am interested. Object.observe() does support filtering, but is limited to filters only the type of change, not the attribute being changed:
  Object.observe(el, function(changes){
    changes.forEach(function(change){
      input.value = change.object[attr];
    });
  }, ['update']);
Since everything in this particular Polymer element is an update, this does not have the desired effect. All changes are sent to the observer callback.

In other words, for my particular use-case, MutationObserver and Object.observe are identical—requiring identical callback handlers to update the form element. And, since MutationObserver (currently) has broader support, I will likely stick with it in the book.


Day #3

Saturday, November 22, 2014

Testing Polymer Updates to Other Elements


Yesterday, I committed a programming cardinal sin. Polymer saw a new release and my code broke. That will happen and is hardly a sin. I fixed my code to address the issue and pushed the fix to the code repository. Again, not a sin.

The sin that I need to confess? I pushed the fix without an accompanying test.

For shame. My penance, of course, is to write a test—and make sure that the test really captures the failures so that I never see them again.

The broken code from last night involved a MutationObserver that synchronizes values from a Polymer element with a plain-old form <input>. The fix was in both the Polymer code (I was not publishing the attribute properly) and in the MutationObserver code which was using some very old wait-for-Polymer setup. The new observer code looks like:
Polymer.whenPolymerReady(function(){
  var input = document.querySelector('#pizza_value');
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.attributeName == 'state') {
    });
  });
  observer.observe(document.querySelector('x-pizza'), {attributes: true});
});
That is nice, but tied to the implementation of my particular code: the #pizza_value, x-pizza, and state values are all hard-coded. That is not great in production code, but for example code in the Patterns in Polymer book, it is fine. Except for testing.

I really want to test the inside of that, so I refactor it as:
Polymer.whenPolymerReady(function(){
  var input = document.querySelector('#pizza_value'),
      el = document.querySelector('x-pizza');
  syncInputFromAttr(input, el, 'state');
});

function syncInputFromAttr(input, el, attr) {
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.attributeName == attr) {
        input.value = mutation.target[attr];
      }
    });
  });
  observer.observe(el, {attributes: true});
}
That should make it easier to test and I think easier to explain in the book (if nothing else, I can split the polymer-ready and observer discussion apart).

Now, let's see if I can test that...

I am still using Karma and Jasmine for testing. I start in my Karma configuration, to which I add this synchronization script to the list of files to load:
    files: [
      'bower_components/webcomponentsjs/webcomponents.js',
      'test/PolymerSetup.js',
      'scripts/sync_polymer_in_form.js',
      {pattern: 'elements/**', included: false, served: true},
      {pattern: 'bower_components/**', included: false, served: true},
      'test/**/*Spec.js'
    ]
That generates an error:
Chrome 39.0.2171 (Linux) ERROR
  Uncaught NotFoundError: Failed to execute 'observe' on 'MutationObserver': The provided node was null.
  at /home/chris/repos/polymer-book/book/code-js/plain_old_forms/scripts/sync_polymer_in_form.js:16
This is because the elements in the when-polymer-ready block do not exist in my tests. I am loath to add conditionals into the actual book code as it is messy to filter out of the book. Instead, I cheat by maybe-adding the <x-pizza> element needed by the smoke test:
document.addEventListener("DOMContentLoaded", function(event) {
  var el = document.querySelector('x-pizza');
  if (el) return;

  document.body.appendChild(document.createElement('x-pizza'));
});
My existing (minimal) test again runs without error, so now I need to write an actual test. I will create an <input> element to synchronize to the value of my Polymer element's state attribute. At the end of the test, the input should have a value of "pepperoni" to reflect the change to the <x-pizza> element:
  describe('syncing <input> values', function(){
    var input;
    // Need setup here....

    it('updates the input', function(){
      expect(input.value).toEqual('pepperoni');
    });
  });
Now, I just need the setup to create the <input> and connect it to the already created <x-pizza> element. Something like the following ought to do the trick:
  describe('syncing <input> values', function(){
    var input;

    beforeEach(function(done){
      input = document.createElement('input');
      container.appendChild(input);

      syncInputFromAttr(input, el, 'state');

      el.model.firstHalfToppings.push('pepperoni');
      el.async(done);
    });

    it('updates the input', function(){
      expect(input.value).toEqual('pepperoni');
    });
  });
Creating the <input> element and appending it to the test's containing <div> is easy enough. Connecting the <input> element's value to the <x-pizza> element's state attribute is done by the newly created syncInputFromAttr(). Lastly, I need to update the internal state of the <x-pizza>, which can be done via the firstHalfTopping property.

The async() callback of Jasmine's done() is the only real trick. When Polymer is done updating the internal state, its bound variables, and the UI, then it calls the callback supplied to async(). By supplying Jasmine's done(), I tell the tests to wait to execute until Polymer has completely updated the element—and allowed the Mutation Observer to see the change and do the same.

Happily, this works as desired:
SUCCESS <x-pizza> element content has a shadow DOM
SUCCESS <x-pizza> syncing <input> values updates the input
More importantly, I can break the test by reverting last night's fixes (not publishing the attribute or using old watch code). Since I can break the test like this, I know that I have successfully captured the error that I previously missed. Better yet, I know that my code will never fail like this again.

Up tomorrow: a little refactoring with Object.observe().



Patterns in Polymer




Day #2

Friday, November 21, 2014

No Idea How That Ever Worked


Again, things are broken in Patterns in Polymer. I would like to get it mostly in order by the end of the month, so having the latest Polymer expose yet more problems is less than ideal. Hopefully these are the easy kinds of problems (they never are).

Actually, there is some hope this time around. All of the book's tests are passing. Even though the tests could be better in places, that still counts for something. The actual problems occur in a handful of the JavaScript smoke tests. In addition to unit and/or acceptance tests, the code for each chapter includes an index.html page that I normally access with the aid of Python's simple HTTP server. Only they stopped working for some reason.

I am unsure if there is a common thread, but some of the pages are just blank. Looking through the code, I see that I am definitely using the correct life-cycle methods:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="normal-changes">
  <template>
  </template>
  <script>
    Polymer('normal-changes', {
      // ...
      attached: function() {
        console.log('yo')
        // ...
    };
  </script>
</polymer-element>
Console debugging verifies that the Polymer element is being created properly. The page is just blank for some reason...

D'oh!

It is blank because the smoke test page contains nothing but a Polymer element wrapping a content-editable field:
  <body>
    <div class=container>
      <normal-changes>
        <div contenteditable>
          <!-- ... -->
        </div>
      </normal-changes>
    </div>
  </body>
Amazingly that worked at one point. The problem is not the page's HTML, but rather the Polymer element's <template>, which lacks a <content> element into which the page can project the wrapped content. The fix is to simply add the tag:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="normal-changes">
  <template>
    <content></content>
  </template>
  <script>
    Polymer('normal-changes', {
      // ...
      attached: function() {
        console.log('yo')
        // ...
    };
  </script>
</polymer-element>
A reload now includes the Polymer wrapped content:



I have no idea how that ever worked, but Polymer must have been OK with it at one point.

Most of the other changes are along the same lines—silly things that I just missed or I cannot figure out how they ever worked. That said, the plain-old forms chapter was just broken. I definitely need to revisit the test code on that tomorrow. For now, I get it working through a combination of reflecting changes in the attribute and better mutation observing in the main page.

I am publishing the current pizza state in the state attribute. For changes to be reflected, I need to explicitly mark it as such:
Polymer('x-pizza', {
  // ...
  publish: {
    state: {
      value: '',
      reflect: true
    }
  },
  // ...
});
The chapter still recommends watching that value so that changes can be synchronized in the containing form. Mutation observing seems to have gotten a little easier thanks to whenPolymerReady():
Polymer.whenPolymerReady(function(){
  var input = document.querySelector('#pizza_value');
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.attributeName == 'state') {
        input.value = mutation.target[mutation.attributeName];
      }
    });
  });
  observer.observe(document.querySelector('x-pizza'), {attributes: true});
});
The previous version of that code required three separate functions and some convoluted wait-for-polymer logic. This seems much nicer (and closer to the Dart solution).

At any rate, I have all of the smoke tests working. I will revisit the tests for the Forms chapter tomorrow.


Day #1

Tuesday, October 7, 2014

Generating Super Simple Polymer (JS)


I really want a minimal Polymer code generator. I thought Yeoman might have something to fit my needs, but upon further review, I am not so sure.

I missed some of what Yeoman's Polymer was doing yesterday. Most the sub-generators wind up installing a lot. I had thought yo polymer:seed might be the answer to my needs. Even though it installed testing stuff too soon (and the wrong testing library) for my tastes, the rest was close. But it turns out that it just installs crazy amounts of dependencies in the parent directory thanks to the hidden .bowerrc file that it includes:
{
  "directory": "../"
}
The end result is 9 directories containing 52 files:
$ tree       
.
├── core-component-page
│   └── ...
├── elements
│   ├── bower.json
│   ├── demo.html
│   ├── index.html
│   ├── README.md
│   ├── test
│   │   ├── index.html
│   │   ├── tests.html
│   │   └── x-pizza-basic.html
│   ├── x-pizza.css
│   └── x-pizza.html
├── platform
│   ├── platform.js
│   └── ...
├── polymer
│   ├── polymer.html
│   ├── polymer.js
│   └── ...
└── polymer-test-tools
    ├── chai
    │   └── ...
    ├── karma-common.conf.js
    ├── mocha
    │   ├── mocha.js
    │   └── ...
    └── ...

9 directories, 52 files
My goal here is to generate the minimal setup required to start coding a Polymer element. I need to keep it small so that my screencasts are short—both on time and concepts.

The end result of my first screencast should be something like:
$ tree
.
├── index.html
├── elements
│   ├── x-pizza.html
│   └── x_pizza.js
├── bower.json
└── bower_components
    └── ...
I should be able to run a python simple HTTP server in the root and see the <x-pizza> element working in the index.html page.

So, I am going to let Emacs generate the index.html page. I will likely stick with a very simple HTML skeleton and add the platform.js <script> tag and the element link by hand (or keyboard shortcut). I tend to think of these as important enough to type out by hand.

For the Bower stuff, I think I will approach it by creating a minimal bower.json on the command-line:
$ echo '{"name": "x_pizza"}' > bower.json
Then install and save my Polymer dependencies:
$ bower install -S Polymer/polymer
bower polymer#*                 cached git://github.com/Polymer/polymer.git#0.4.2
...
polymer#0.4.2 bower_components/polymer
So that takes care of the index.html page and Bower, what about the element? The polymer:el generator is close to what I need, but... there are two things that give me pause.

First, the element is generated in the wrong directory (I think). Where I would prefer it go directly in elements, polymer:el puts it in app/elements/<name-of-element>/:
$ yo polymer:el x-pizza
? Would you like an external CSS file for this element? No
? Would you like to include an import in your elements.html file? No
   create app/elements/x-pizza/x-pizza.html
I could move it, but then I would have to change the polymer.html import in the generated output since bower_components will wind up in the parent directory instead of the grandparent directory:
<link rel="import" href="../../bower_components/polymer/polymer.html">
<polymer-element name="<%= elementName %>" attributes="">
  <template><% if (!externalStyle) { %>
    <-- ... -->
  </template>
  <script>
    (function () {
      'use strict';

      Polymer({
        // define element prototype here
      });

    })();
  </script>
</polymer-element>
Even that I could live with, but I think including the Polymer prototype definition in the HTML, coupled with my other pet peeves, is what will keep me from Yeoman. Emacs has a nifty JavaScript mode that I would prefer to use in a separate .js file. There are multi-minor mode solutions for working with both HTML and JavaScript in the same file, but something about it just does not feel as nice.

So I think I will wind up relying on Emacs for this as well. Speaking of Emacs, I need to dust off some of my extremely rusty elisp to modify the built-in HTML mode to accommodate <polymer-element> definition elements. Something like this in my .emacs ought to work:
(setq html-tag-alist (cons '("polymer-element" (\n
        "<template>\n    " _
        "\n  </template>\n"
        "  <script src=\"x_element.js\"></script>"
  ))
 html-tag-alist
))
When I used the HTML mode insert-element command for polymer-element, the above template will be inserted with the cursor being placed after the initial <template> tag (by virtue of the underscore). I may fiddle with yasnippet as an alternative, but I have never been able to get on board with snippets. I can remember the most obscure Emacs keyboard combinations, but I have never been able to use snippets often enough to remember them.

Regardless, I think that I will not be using Yeoman in the screencasts. It sets up beautiful Polymer elements and development layouts, but feels too noisy for my needs. Hopefully this low-key approach will work better.

Day #206

Monday, October 6, 2014

Simple Polymer Generators with Yeoman


I shaved 3 minutes off the Patterns in Polymer screencast that deals with the initial Polymer element generation in Dart. I still need to find another 3 minutes, but that is a good start. Good enough that I would to explore doing the same with JavaScript Polymer elements. Since I would like to stick with standard, maintained generators, this means Yeoman.

In all honesty, I have never used Yeoman. I tend to prefer writing initial applications / widgets by hand. I regard code that I maintain directly as too important to trust without hand-writing. If code is a starship, then I side with Sarris that a captain “know every bolt, every weld in his ship.” I might treat the inner workings of bolt creation and welding tools as abstractions that I can delve into later, but I will understand them well enough and know where they reside in my code.

That said, I don't hate code generation. Especially if they save me 3+ minutes in screencasts.

So I start by installing Yeoman:
$ npm install -g yo
/home/chris/local/node-v0.10.20/bin/yo -> /home/chris/local/node-v0.10.20/lib/node_modules/yo/cli.js

> yo@1.3.0 postinstall /home/chris/local/node-v0.10.20/lib/node_modules/yo
> node ./scripts/doctor

[Yeoman Doctor] Everything looks alright!

yo@1.3.0 /home/chris/local/node-v0.10.20/lib/node_modules/yo
├── ...
└── yeoman-generator@0.17.7 (...)
Next, I install the Polymer generator for Yeoman:
$ npm install -g generator-polymer
...
generator-polymer@0.5.1 /home/chris/local/node-v0.10.20/lib/node_modules/generator-polymer
...
With that, I am ready to run the generator in a new (temporary) project:
$ cd ~/tmp
$ mkdir x_pizza
$ cd !$
$ yo polymer
? Would you like to include core-elements? No
? Would you like to include paper-elements? No
? Would you like to use SASS/SCSS for element styles? No
   create .gitignore
   create .gitattributes
   create .bowerrc
   create bower.json
   create .jshintrc
   create .editorconfig
   create Gruntfile.js
   create package.json
   create app/404.html
   create app/favicon.ico
   create app/robots.txt
   create app/styles/main.css
   create app/scripts/app.js
   create app/.htaccess
   create app/elements/elements.html
   create app/elements/yo-list/yo-list.html
   create app/elements/yo-list/yo-list.css
   create app/elements/yo-greeting/yo-greeting.html
   create app/elements/yo-greeting/yo-greeting.css
   create app/index.html
   create app/test/index.html
   create app/test/tests.html
   create app/test/yo-greeting-basic.html
   create app/test/yo-list-basic.html
...
Yikes! That is a lot of infrastructure. I would rather focus on teaching Polymer than Polymer-on-Yeoman in these screencasts. The default page in app/index.html file and the sample Polymer element that it includes are nice demos, but I have the feeling that I may end up deleting a bunch of this for use in the screencast. On the other hand, it is nice that it creates a useful bower.json and runs bower.json. I will have to consider this.

The element generator is pretty clean however:
$ yo polymer:el x-pizza
? Would you like an external CSS file for this element? No
? Would you like to include an import in your elements.html file? No
   create app/elements/x-pizza/x-pizza.html
$ cat app/elements/x-pizza/x-pizza.html
<link rel="import" href="../../bower_components/polymer/polymer.html">
<polymer-element name="x-pizza" attributes="">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
  </template>
  <script>
    (function () {
      'use strict';
      Polymer({
        // define element prototype here
      });
    })();
  </script>
</polymer-element>
I might quibble with the Polymer class being inline instead of in a separate script file, but that is close to what I want.

But in the end, I think the seed-element generator is closer to what I need for the screencasts. There is less infrastructure, but it still generates a smoke test page and a skeleton for the template:
$ cd ..    
$ rm -rf x_pizza 
$ mkdir x_pizza
$ cd !$
$ yo polymer:seed

     _-----_
    |       |    .--------------------------.
    |--(o)--|    | Out of the box I include |
   `---------´   |        the Polymer       |
    ( _´U`_ )    |       seed-element.      |
    /___A___\    '--------------------------'
     |  ~  |     
   __'.___.'__   
 ´   `  |° ´ Y ` 

? What is your GitHub username? eee-c
? What is your element's name: x-pizza
   create .gitignore
   create .gitattributes
   create .bowerrc
   create bower.json
   create .jshintrc
   create .editorconfig
   create x-pizza.css
   create x-pizza.html
   create index.html
   create demo.html
   create README.md
   create test/index.html
   create test/x-pizza-basic.html
   create test/tests.html
...
This also runs bower install, so I will be ready to start coding right away. The demo.html page that uses the generated <x-pizza> element is welcomingly brief. The generated element, however, is crazy long—it even includes a fireLasers() method.

So it does not seem that generating scaffold code in JavaScript is going to be as easy in JavaScript as in Dart. Hopefully I can work with it to keep the running time under 10 minutes. Time will tell.


Day #205