scheduled_value 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 03256b1fb2793955ad190fb8e3843c2284ebcbec
4
+ data.tar.gz: 7de94668cee16c3ec62273d27e968cc0e3468aed
5
+ SHA512:
6
+ metadata.gz: 3c0ff1582c58ef50cd518efc791b5ce467359a3bb0adb0b4155801a8f0e91c471775e9bd34c9ff1fb7a72b391281824372701fb1299d995933f497c1628624fb
7
+ data.tar.gz: 09b4918af958643332601f717edcd4a83a8ab7a841cf9e3b556f376aac264e030b1d4c8867f5f756408a3c40d55133901d7f4615290f6e7230f5a79aa65ace3c
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,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.1
4
+ before_install: gem install bundler -v 1.10.5
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 1.0.0 - December 24, 2016
2
+
3
+ * Initial public release.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in scheduled_value.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # ScheduledValue
2
+
3
+ ![Codeship test status](https://codeship.com/projects/165d5000-ac28-0134-2a75-16abc9b20ebe/status?branch=master)
4
+
5
+ ScheduledValue provides a set of Ruby classes for representing values that change over time based on a schedule. One way to think about this is that for a regular variable, you could reasonably ask "what is its value?" For a ScheduledValue, you have to ask "what is its value *right now*?" Some examples of this might be:
6
+
7
+ * an on-call rotation (who is on call *right now*?)
8
+ * pricing for an event with early bird tickets (what is the price *right now*?)
9
+
10
+ ScheduledValue also provides a `ScheduledValue::Timespan` class that represents a span of time, which may have a defined start point, a defined end point, both, or neither. `ScheduledValue::Timespan`s are Ruby `Comparable` objects.
11
+
12
+ All classes in the ScheduledValue gem are designed to be serializable to and from dictionary-like formats, such as JSON and YAML. This makes it easy to use them with ActiveRecord's `serialize` class method and other similar techniques for storing them in a database column.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'scheduled_value'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install scheduled_value
29
+
30
+ ## Development
31
+
32
+ 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.
33
+
34
+ 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).
35
+
36
+ ## Contributing
37
+
38
+ Bug reports and pull requests are welcome on GitHub at https://github.com/neinteractiveliterature/scheduled_value.
39
+
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 "scheduled_value"
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,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,7 @@
1
+ require "scheduled_value/version"
2
+ require "scheduled_value/timespan"
3
+ require "scheduled_value/timespan_with_value"
4
+ require "scheduled_value/scheduled_value"
5
+
6
+ module ScheduledValue
7
+ end
@@ -0,0 +1,106 @@
1
+ module ScheduledValue
2
+ class ScheduledValue
3
+ attr_reader :timespans
4
+
5
+ def self.timespan_with_value_class
6
+ TimespanWithValue
7
+ end
8
+
9
+ def self.always(value)
10
+ timespan = timespan_with_value_class.new(start: nil, finish: nil, value: value)
11
+ self.new(timespans: [timespan])
12
+ end
13
+
14
+ def from_json(json, include_root = false)
15
+ hash = JSON.parse(json)
16
+ hash = hash.values.first if include_root
17
+ self.attributes = hash
18
+ self
19
+ end
20
+
21
+ def initialize(timespans: [])
22
+ self.timespans = timespans
23
+ end
24
+
25
+ def attributes
26
+ {
27
+ timespans: timespans
28
+ }
29
+ end
30
+
31
+ def attributes=(attributes)
32
+ self.timespans = attributes[:timespans] || attributes['timespans']
33
+ end
34
+
35
+ def timespans=(timespan_values)
36
+ @timespans = SortedSet.new
37
+
38
+ timespan_values.each do |timespan_value|
39
+ timespan = case timespan_value
40
+ when self.class.timespan_with_value_class then timespan_value
41
+ when Hash then
42
+ attributes = timespan_value.each_with_object({}) { |(key, value), hash| hash[key.to_sym] = value }
43
+ self.class.timespan_with_value_class.new(**attributes)
44
+ end
45
+
46
+ self << timespan if timespan
47
+ end
48
+
49
+ @timespans
50
+ end
51
+
52
+ def <<(timespan_with_value)
53
+ overlapping_timespan = timespan_overlapping(timespan_with_value)
54
+ raise "Timespan for value #{overlapping_timespan} would overlap with #{timespan_with_value}" if overlapping_timespan
55
+
56
+ @timespans << timespan_with_value
57
+ @timespans_array = nil
58
+
59
+ timespan_with_value
60
+ end
61
+
62
+ def set_value_for_timespan(start, finish, value)
63
+ self << self.class.timespan_with_value_class.new(start: start, finish: finish, vaule: value)
64
+ value
65
+ end
66
+
67
+ def value_at(timestamp)
68
+ timespan = timespan_containing(timestamp)
69
+ raise "No timespan found for #{timestamp}" unless timespan
70
+
71
+ timespan.value
72
+ end
73
+
74
+ def next_value_change_after(timestamp)
75
+ timespan = timespan_containing(timestamp)
76
+ return nil unless timespan
77
+
78
+ timespan.finish
79
+ end
80
+
81
+ def covers_all_time?
82
+ timespans_array = timespans.to_a
83
+ !timespans_array.first.start && !timespans_array.last.finish
84
+ end
85
+
86
+ private
87
+
88
+ def timespans_array
89
+ @timespans_array ||= timespans.to_a
90
+ end
91
+
92
+ def timespan_containing(timestamp)
93
+ timespans_array.bsearch do |timespan|
94
+ if timespan.contains? timestamp
95
+ 0
96
+ else
97
+ (timespan <=> timestamp) * -1
98
+ end
99
+ end
100
+ end
101
+
102
+ def timespan_overlapping(timespan)
103
+ timespans.find { |my_timespan| my_timespan.overlaps? timespan }
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,95 @@
1
+ module ScheduledValue
2
+ class Timespan
3
+ include Comparable
4
+ attr_accessor :start, :finish
5
+
6
+ def initialize(start: nil, finish: nil)
7
+ @start = start
8
+ @finish = finish
9
+
10
+ raise "Finish must be after start" if start && finish && start >= finish
11
+ end
12
+
13
+ def attributes
14
+ {
15
+ start: start,
16
+ finish: finish
17
+ }
18
+ end
19
+
20
+ def start=(value)
21
+ @start = convert_time_value(value)
22
+ end
23
+
24
+ def finish=(value)
25
+ @finish = convert_time_value(value)
26
+ end
27
+
28
+ def contains?(timestamp)
29
+ return false if start && timestamp < start
30
+ return false if finish && timestamp >= finish
31
+
32
+ true
33
+ end
34
+
35
+ def overlaps?(other)
36
+ return false if finish && other.start && other.start >= finish
37
+ return false if start && other.finish && start >= other.finish
38
+
39
+ true
40
+ end
41
+
42
+ def <=>(other)
43
+ case other
44
+ when Timespan then compare_timespan(other)
45
+ when Date, Time, DateTime then compare_datetime(other)
46
+ end
47
+ end
48
+
49
+ def inspect
50
+ "#<#{self.class.name}: #{self}>"
51
+ end
52
+
53
+ def to_s(format = nil)
54
+ "#{start_description(format)} #{finish_description(format)}"
55
+ end
56
+
57
+ def start_description(format = nil)
58
+ if start
59
+ "from #{start.to_s(format)}"
60
+ else
61
+ 'anytime'
62
+ end
63
+ end
64
+
65
+ def finish_description(format = nil)
66
+ if finish
67
+ "up to #{finish.to_s(format)}"
68
+ elsif start
69
+ "indefinitely"
70
+ end
71
+ end
72
+
73
+ private
74
+ def compare_timespan(other)
75
+ return 0 if other.start == start && other.finish == finish
76
+ return nil if other.overlaps?(self)
77
+ return -1 if finish && other.start && other.start >= finish
78
+ return 1 if start && other.finish && other.finish <= start
79
+ end
80
+
81
+ def compare_datetime(other)
82
+ return nil if contains?(other)
83
+ return -1 if finish && other >= finish
84
+ return 1 if start && other < start
85
+ 0
86
+ end
87
+
88
+ def convert_time_value(value)
89
+ case value
90
+ when String then Time.iso8601(value)
91
+ else value
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,18 @@
1
+ module ScheduledValue
2
+ class TimespanWithValue < Timespan
3
+ attr_accessor :value
4
+
5
+ def initialize(start: nil, finish: nil, value: nil)
6
+ super(start: start, finish: finish)
7
+ @value = value
8
+ end
9
+
10
+ def attributes
11
+ super.merge(value: value)
12
+ end
13
+
14
+ def to_s(format = nil)
15
+ "#{value} #{super}"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module ScheduledValue
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'scheduled_value/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "scheduled_value"
8
+ spec.version = ScheduledValue::VERSION
9
+ spec.authors = ["Nat Budin"]
10
+ spec.email = ["natbudin@gmail.com"]
11
+
12
+ spec.summary = %q{Ruby classes for values that change over time}
13
+ spec.description = %q{ScheduledValue allows you to create and store values which change over time based on a schedule.}
14
+ spec.homepage = "https://github.com/neinteractiveliterature/scheduled_value"
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_development_dependency "bundler", "~> 1.10"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest"
25
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scheduled_value
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Nat Budin
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-12-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: ScheduledValue allows you to create and store values which change over
56
+ time based on a schedule.
57
+ email:
58
+ - natbudin@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".travis.yml"
65
+ - CHANGELOG.md
66
+ - Gemfile
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - lib/scheduled_value.rb
72
+ - lib/scheduled_value/scheduled_value.rb
73
+ - lib/scheduled_value/timespan.rb
74
+ - lib/scheduled_value/timespan_with_value.rb
75
+ - lib/scheduled_value/version.rb
76
+ - scheduled_value.gemspec
77
+ homepage: https://github.com/neinteractiveliterature/scheduled_value
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.6.4
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Ruby classes for values that change over time
101
+ test_files: []