timed 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 91f02760afcbcaf91d29ddf7fe817fbc0ec7dcf5
4
+ data.tar.gz: b7220abee0fce727c26f6961ebbffbc737cc7eb2
5
+ SHA512:
6
+ metadata.gz: db98749f2945a92b8ae56e58bdeb08d05c4b417fe8e2d9fc627f9de9cf39becf99000189c9b32b3eda704299eb8ac045ab23ba5f42e96191744534559f828c4d
7
+ data.tar.gz: 076b4b45416d418b1554453f5a97b82c717a242cbff240c827111a527bb0ac24e74b822d03498be917a020f84e035fe4cdec325b8d44b44c8db9e49f96abf97e
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in timed.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Sebastian Lindberg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # Timed
2
+
3
+ [![Build Status](https://travis-ci.org/seblindberg/ruby-timed.svg?branch=master)](https://travis-ci.org/seblindberg/ruby-timed)
4
+ [![Coverage Status](https://coveralls.io/repos/github/seblindberg/ruby-timed/badge.svg?branch=master)](https://coveralls.io/github/seblindberg/ruby-timed?branch=master)
5
+ [![Inline docs](http://inch-ci.org/github/seblindberg/ruby-timed.svg?branch=master)](http://inch-ci.org/github/seblindberg/ruby-timed)
6
+
7
+ Gem for working with timed, ordered items. Still early days.
8
+
9
+ The basic building block is the `Timed::Item`. These begin and end somewhere in time and can thus be related to each other. Several items can then be combined into a `Timed::Sequence`. This object guarantees that the items in it are non overlapping and ordered chronologically.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'timed'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install timed
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ require 'timed'
31
+
32
+ # Create an empty
33
+ sequence = Timed::Sequence.new
34
+
35
+ # Add a couple of items. Any object that implements #begin
36
+ # and #end can be added. Internally it is converted to a
37
+ # Timed::Item.
38
+ sequence << 10..20
39
+ sequence << 30..40
40
+
41
+ # Calculate the time occupied by the items in the sequence
42
+ sequence.time # => 20
43
+ ```
44
+
45
+ ## Development
46
+
47
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
48
+
49
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
50
+
51
+ ## Contributing
52
+
53
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/timed.
54
+
55
+
56
+ ## License
57
+
58
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
59
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "timed"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/timed/item.rb ADDED
@@ -0,0 +1,96 @@
1
+ module Timed
2
+ # Item
3
+ #
4
+ # The Timed Item is a Moment that can be chained to others to form a sequence.
5
+ # Importantly, items in a sequence are guaranteed to be sequential and non
6
+ # overlapping.
7
+
8
+ class Item < Linked::Item
9
+ include Moment
10
+ # The value field is used to store the timespan and shuold not be accessed
11
+ # directly.
12
+
13
+ protected :value
14
+ private :value=
15
+
16
+ # Creates a new Timed Item from a timespan. A timespan is any object that
17
+ # responds to #begin and #end with two numerical values. Note that the end
18
+ # must occur after the start for the span to be valid.
19
+ #
20
+ # If given a second argument, the two will instead be interpreted as the
21
+ # begin and end time.
22
+ #
23
+ # timespan - object responding to #begin and #end.
24
+ # end_at - if given, it togheter with the first argument will be used as the
25
+ # begin and end time for the item
26
+
27
+ def initialize(timespan, end_at = nil)
28
+ if end_at
29
+ begin_at = timespan
30
+ else
31
+ begin_at = timespan.begin
32
+ end_at = timespan.end
33
+ end
34
+
35
+ raise TypeError unless begin_at.is_a?(Numeric) && end_at.is_a?(Numeric)
36
+ raise ArgumentError if end_at < begin_at
37
+
38
+ super begin_at..end_at
39
+ end
40
+
41
+ # Returns the time when the item starts.
42
+
43
+ def begin
44
+ value.begin
45
+ end
46
+
47
+ # Returns the time when the item ends.
48
+
49
+ def end
50
+ value.end
51
+ end
52
+
53
+ # Returns a new Item in the intersection
54
+ #
55
+ # other - object that implements both #begin and #end.
56
+
57
+ def intersect(other)
58
+ begin_at = self.begin >= other.begin ? self.begin : other.begin
59
+ end_at = self.end <= other.end ? self.end : other.end
60
+
61
+ begin_at <= end_at ? self.class.new(begin_at, end_at) : nil
62
+ end
63
+
64
+ alias & intersect
65
+
66
+ # Inserts an item after this one and before the next in the sequence. The
67
+ # new item may not overlap with the two it sits between. A RuntimeError will
68
+ # be raised if it does.
69
+ #
70
+ # If the given object is an Item it will be inserted directly. Otherwise, if
71
+ # the object responds to #begin and #end, a new Item will be created from
72
+ # that timespan.
73
+
74
+ def append(other)
75
+ raise RuntimeError unless before? other
76
+ raise RuntimeError unless last? || after?(self.next)
77
+
78
+ super
79
+ end
80
+
81
+ # Inserts an item before this one and after the previous in the sequence.
82
+ # The new item may not overlap with the two it sits between. A RuntimeError
83
+ # will be raised if it does.
84
+ #
85
+ # If the given object is an Item it will be inserted directly. Otherwise, if
86
+ # the object responds to #begin and #end, a new Item will be created from
87
+ # that timespan.
88
+
89
+ def prepend(other)
90
+ raise RuntimeError unless after? other
91
+ raise RuntimeError unless first? || before?(previous)
92
+
93
+ super
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timed
4
+ # Timed Moment
5
+ #
6
+ # By including this module into any object that responds to #begin and #end,
7
+ # it can be compared with other moments in time.
8
+ #
9
+ # To fully support the Moment module the following must hold:
10
+ # a) #begin returns a Numeric value
11
+ # b) #end returns a Numeric value larger than (or equal to) begin
12
+ # c) a new object can be created by a single range-like argument
13
+
14
+ module Moment
15
+ # Returns the moment duration.
16
+
17
+ def duration
18
+ self.end - self.begin
19
+ end
20
+
21
+ # Returns true if the two moments begin and end on exactly the same time.
22
+ #
23
+ # other - object that implements both #begin and #end.
24
+
25
+ def ==(other)
26
+ self.begin == other.begin && self.end == other.end
27
+ rescue NoMethodError
28
+ false
29
+ end
30
+
31
+ # Returns true if the moment ends before the other one begins. If given a
32
+ # numeric value it will be treated like instantaneous moment in time.
33
+ #
34
+ # other - object that implements #begin, or a numeric value.
35
+
36
+ def before?(other)
37
+ time = other.is_a?(Numeric) ? other : other.begin
38
+ self.end <= time
39
+ end
40
+
41
+ # Returns true if the moment begins after the other one ends. If given a
42
+ # numeric value it will be treated like instantaneous moment in time.
43
+ #
44
+ # other - object that implements #end, or a numeric value.
45
+
46
+ def after?(other)
47
+ time = other.is_a?(Numeric) ? other : other.end
48
+ self.begin >= time
49
+ end
50
+
51
+ # Returns true if the moment overlaps with the other one. If given a
52
+ # numeric value it will be treated like instantaneous moment in time.
53
+ #
54
+ # other - object that implements both #begin and #end, or a numeric value.
55
+
56
+ def during?(other)
57
+ if other.is_a? Numeric
58
+ other_begin = other_end = other
59
+ else
60
+ other_begin, other_end = other.begin, other.end
61
+ end
62
+
63
+ self_begin, self_end = self.begin, self.end
64
+
65
+ # Check if either of the two items begins during the
66
+ # span of the other
67
+ other_begin <= self_begin && self_begin <= other_end ||
68
+ self_begin <= other_begin && other_begin <= self_end
69
+ end
70
+
71
+ # Returns a new moment in the intersection
72
+ #
73
+ # other - object that implements both #begin and #end.
74
+
75
+ def intersect(other)
76
+ begin_at = self.begin >= other.begin ? self.begin : other.begin
77
+ end_at = self.end <= other.end ? self.end : other.end
78
+
79
+ begin_at <= end_at ? self.class.new(begin_at..end_at) : nil
80
+ end
81
+
82
+ alias & intersect
83
+
84
+ # Compare the moments with others.
85
+ #
86
+ # Return -1 if the item is before the other, 0 if they overlap and 1 if the
87
+ # item is after the other.
88
+
89
+ # def <=>(other)
90
+ # case
91
+ # when before?(other) then -1
92
+ # when after?(other) then 1
93
+ # else 0
94
+ # end
95
+ # end
96
+
97
+ # Override the default implementation and provide a more useful
98
+ # representation of the Timed Moment, including when it begins and ends.
99
+ #
100
+ # Example
101
+ # moment.inspect # => "ClassName 12.20 -> 15.50"
102
+ #
103
+ # Returns a string representation of the object.
104
+
105
+ def inspect
106
+ format '%s %7.2f -> %.2f', self.class.name, self.begin, self.end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,154 @@
1
+ module Timed
2
+ # Sequence
3
+ #
4
+ # This class implements a sequence of Timed Items. Any object that implements
5
+ # the methods #begin and #end can be push to the sequence. Note that the items
6
+ # must be inserted in chronological order, or the sequence will raise an
7
+ # exception.
8
+ #
9
+ # Example
10
+ # sequence = Timed::Sequence.new
11
+ # sequence << 2..3
12
+ # sequence.prepend Timed::Item.new 1..2 # Same result as above
13
+ #
14
+ # A sequence can also be treated like a Moment and be compared, in time, with
15
+ # other compatible objects.
16
+
17
+ class Sequence
18
+ include Moment
19
+ include Linked::List
20
+
21
+ # Returns the time at which the first item in the sequence, and therefore
22
+ # the sequence as a whole, begins. If the sequence is empty, by convention,
23
+ # it both begins and ends at time 0, giving it a 0 length.
24
+
25
+ def begin
26
+ empty? ? 0 : first.begin
27
+ end
28
+
29
+ # Returns the time at which the last item in the sequence, and therefore
30
+ # the sequence as a whole, ends. If the sequence is empty, by convention,
31
+ # it both begins and ends at time 0, giving it a 0 length.
32
+
33
+ def end
34
+ empty? ? 0 : last.end
35
+ end
36
+
37
+ # Returns the total time made up by the items
38
+
39
+ def time
40
+ each_item.reduce(0) { |a, e| a + e.duration }
41
+ end
42
+
43
+ # Extends the standard behaviour of Linked::List#first with the option of
44
+ # only returning items that begin after a specified time.
45
+ #
46
+ # after - a time after which the returned item(s) must occur.
47
+ #
48
+ # Returns one or more items, or nil if there are no items after the given
49
+ # time.
50
+
51
+ def first(n = 1, after: nil, &block)
52
+ return super(n, &block) unless after
53
+
54
+ if include? after
55
+ first_item_after after, n
56
+ else
57
+ super(n) { |item| item.after? after }
58
+ end
59
+ end
60
+
61
+ # Extends the standard behaviour of Linked::List#last with the option of
62
+ # only returning items that end before a specified time.
63
+ #
64
+ # after - a time after which the returned item(s) must occur.
65
+ #
66
+ # Returns one or more items, or nil if there are no items before the given
67
+ # time.
68
+
69
+ def last(n = 1, before: nil, &block)
70
+ return super(n, &block) unless before
71
+
72
+ if include? before
73
+ last_item_before before, n
74
+ else
75
+ super(n) { |item| item.before? before }
76
+ end
77
+ end
78
+
79
+ # This method takes a second sequence and iterates over each intersection
80
+ # between the two. If a block is given, the beginning and end of each
81
+ # intersecting period will be yielded to it. Otherwise an enumerator is
82
+ # returned.
83
+ #
84
+ # other - a sequence, or object that responds to #begin and #end and returns
85
+ # a Timed::Item in response to #first
86
+ #
87
+ # Returns an enumerator unless a block is given, in which case the number of
88
+ # intersections is returned.
89
+
90
+ def intersections(other)
91
+ return to_enum __callee__, other unless block_given?
92
+
93
+ return unless during? other
94
+
95
+ # Sort the first items from each sequence into leading
96
+ # and trailing by whichever begins first
97
+ if self.begin <= other.begin
98
+ item_l, item_t = first, other.first
99
+ else
100
+ item_l, item_t = other.first, first
101
+ end
102
+
103
+ count = 0
104
+
105
+ loop do
106
+ # Now there are three posibilities:
107
+
108
+ # 1: The leading item ends before the trailing one
109
+ # begins. In this case the items do not intersect
110
+ # at all and we do nothing.
111
+ if item_l.end <= item_t.begin
112
+
113
+ # 2: The leading item ends before the trailing one
114
+ # ends
115
+ elsif item_l.end <= item_t.end
116
+ yield item_t.begin, item_l.end
117
+ count += 1
118
+
119
+ # 3: The leading item ends after the trailing one
120
+ else
121
+ yield item_t.begin, item_t.end
122
+ count += 1
123
+
124
+ # Swap leading and trailing
125
+ item_l, item_t = item_t, item_l
126
+ end
127
+
128
+ # Advance the leading item
129
+ item_l = item_l.next
130
+
131
+ # Swap leading and trailing if needed
132
+ item_l, item_t = item_t, item_l if item_l.begin > item_t.begin
133
+ end
134
+
135
+ count
136
+ end
137
+
138
+ # Returns a new sequence with items that make up the intersection between
139
+ # the two sequences.
140
+
141
+ def intersect(other)
142
+ intersections(other)
143
+ .with_object(self.class.new) { |(b, e), a| a << Item.new(b, e) }
144
+ end
145
+
146
+ alias & intersect
147
+
148
+ # More efficient than first calling #intersect and then #time on the result.
149
+
150
+ def intersect_time(other)
151
+ intersections(other).reduce(0) { |a, (b, e)| a + e - b }
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Timed
4
+ VERSION = '0.1.0'
5
+ end
data/lib/timed.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'linked'
2
+ require 'timed/version'
3
+ require 'timed/moment'
4
+ require 'timed/item'
5
+ require 'timed/sequence'
6
+
7
+ module Timed
8
+
9
+ end
data/timed.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'timed/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "timed"
8
+ spec.version = Timed::VERSION
9
+ spec.authors = ["Sebastian Lindberg"]
10
+ spec.email = ["seb.lindberg@gmail.com"]
11
+
12
+ spec.summary = %q{Gem for working with timed, ordered items.}
13
+ #spec.description = %q{TODO: Write a longer description or delete this line.}
14
+ spec.homepage = "https://github.com/seblindberg/ruby-timed"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "linked", "~> 0.1.3"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.12"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.0"
27
+ spec.add_development_dependency "coveralls", "~> 0.8"
28
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: timed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sebastian Lindberg
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-08-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: linked
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: coveralls
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.8'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.8'
83
+ description:
84
+ email:
85
+ - seb.lindberg@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".travis.yml"
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - bin/console
97
+ - bin/setup
98
+ - lib/timed.rb
99
+ - lib/timed/item.rb
100
+ - lib/timed/moment.rb
101
+ - lib/timed/sequence.rb
102
+ - lib/timed/version.rb
103
+ - timed.gemspec
104
+ homepage: https://github.com/seblindberg/ruby-timed
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.5.1
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Gem for working with timed, ordered items.
128
+ test_files: []