repeatable 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +72 -0
  3. data/CHANGELOG.md +18 -1
  4. data/CODE_OF_CONDUCT.md +128 -0
  5. data/Gemfile +2 -0
  6. data/Gemfile.lock +24 -5
  7. data/README.md +27 -8
  8. data/Rakefile +5 -1
  9. data/lib/repeatable/conversions.rb +5 -1
  10. data/lib/repeatable/expression/base.rb +21 -11
  11. data/lib/repeatable/expression/biweekly.rb +14 -6
  12. data/lib/repeatable/expression/date.rb +10 -6
  13. data/lib/repeatable/expression/day_in_month.rb +4 -0
  14. data/lib/repeatable/expression/difference.rb +14 -5
  15. data/lib/repeatable/expression/exact_date.rb +5 -1
  16. data/lib/repeatable/expression/intersection.rb +6 -2
  17. data/lib/repeatable/expression/range_in_year.rb +33 -9
  18. data/lib/repeatable/expression/set.rb +15 -5
  19. data/lib/repeatable/expression/union.rb +5 -1
  20. data/lib/repeatable/expression/weekday.rb +4 -0
  21. data/lib/repeatable/expression/weekday_in_month.rb +14 -1
  22. data/lib/repeatable/expression.rb +1 -0
  23. data/lib/repeatable/last_date_of_month.rb +4 -0
  24. data/lib/repeatable/parse_error.rb +1 -0
  25. data/lib/repeatable/parser.rb +7 -1
  26. data/lib/repeatable/schedule.rb +18 -4
  27. data/lib/repeatable/types.rb +6 -0
  28. data/lib/repeatable/version.rb +2 -1
  29. data/lib/repeatable.rb +4 -0
  30. data/rbi/repeatable.rbi +310 -0
  31. data/repeatable.gemspec +3 -0
  32. data/sorbet/config +3 -0
  33. data/sorbet/rbi/gems/ast.rbi +49 -0
  34. data/sorbet/rbi/gems/coderay.rbi +285 -0
  35. data/sorbet/rbi/gems/commander.rbi +197 -0
  36. data/sorbet/rbi/gems/docile.rbi +36 -0
  37. data/sorbet/rbi/gems/highline.rbi +577 -0
  38. data/sorbet/rbi/gems/method_source.rbi +64 -0
  39. data/sorbet/rbi/gems/parallel.rbi +83 -0
  40. data/sorbet/rbi/gems/parlour.rbi +840 -0
  41. data/sorbet/rbi/gems/parser.rbi +1950 -0
  42. data/sorbet/rbi/gems/pry.rbi +1898 -0
  43. data/sorbet/rbi/gems/rainbow.rbi +118 -0
  44. data/sorbet/rbi/gems/rake.rbi +646 -0
  45. data/sorbet/rbi/gems/regexp_parser.rbi +926 -0
  46. data/sorbet/rbi/gems/repeatable.rbi +13 -0
  47. data/sorbet/rbi/gems/rexml.rbi +583 -0
  48. data/sorbet/rbi/gems/rspec-core.rbi +1919 -0
  49. data/sorbet/rbi/gems/rspec-expectations.rbi +1150 -0
  50. data/sorbet/rbi/gems/rspec-mocks.rbi +1100 -0
  51. data/sorbet/rbi/gems/rspec-support.rbi +280 -0
  52. data/sorbet/rbi/gems/rspec.rbi +15 -0
  53. data/sorbet/rbi/gems/rubocop-ast.rbi +1356 -0
  54. data/sorbet/rbi/gems/rubocop-performance.rbi +487 -0
  55. data/sorbet/rbi/gems/rubocop.rbi +7923 -0
  56. data/sorbet/rbi/gems/ruby-progressbar.rbi +304 -0
  57. data/sorbet/rbi/gems/simplecov-html.rbi +35 -0
  58. data/sorbet/rbi/gems/simplecov.rbi +419 -0
  59. data/sorbet/rbi/gems/simplecov_json_formatter.rbi +47 -0
  60. data/sorbet/rbi/gems/standard.rbi +130 -0
  61. data/sorbet/rbi/gems/unicode-display_width.rbi +20 -0
  62. data/sorbet/rbi/hidden-definitions/errors.txt +4273 -0
  63. data/sorbet/rbi/hidden-definitions/hidden.rbi +9013 -0
  64. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +276 -0
  65. data/sorbet/rbi/sorbet-typed/lib/rake/all/rake.rbi +645 -0
  66. data/sorbet/rbi/sorbet-typed/lib/rspec-core/all/rspec-core.rbi +24 -0
  67. data/sorbet/rbi/sorbet-typed/lib/rubocop/>=1.8/rubocop.rbi +12 -0
  68. data/sorbet/rbi/sorbet-typed/lib/rubocop-performance/~>1.6/rubocop-performance.rbi +149 -0
  69. metadata +61 -6
  70. data/.travis.yml +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f6976240c976a027e08e04dc46dff2a7a420894f089ae7fa8c9fded126bafc6
4
- data.tar.gz: 4e0e0e4d45d72b543549904a9a6f4b041430b9808075ccb64a81583366a4e414
3
+ metadata.gz: 71a9ecf64d973e0e8298628e5ab19ab4025c9c3281a357efaae330f42c9767a0
4
+ data.tar.gz: 190a230b24d32f0df2f6565d35c2da1867d3ae87c82d0e00d98ab16b53baa46c
5
5
  SHA512:
6
- metadata.gz: 2698038cb582f612642c1b2f9c595e3830ff42556fb89eef56d4bbf5e3c34396d318175699ecb74a2c589aefe861cc5c7e17a6bde0b4950b6939421fedcb1260
7
- data.tar.gz: 6a98285ed9058b186651055e5afecae2b2abcb64ca0dbc488b92b76ff6a20da02d4ab1d96f204bd864b210a5d183fdff34db31b82d4b06595e0b7c16f2dc3dcf
6
+ metadata.gz: 8335d2132cd0fc2cdf5d991431bafcec688819b9b2df45090afcdb05886f74632ebb262ae6f014911aa300589a5f1a776d2a5325adb39569824af306cd94bbfc
7
+ data.tar.gz: 26a637424b6a64a3a1a6b6db0ed8fb6c0e1ece5040b15499182fd3e45cdb3bf7298634620d1f3284af1a47f00655f7bac7212bea369fbc5f287e154fc3ce45cf
@@ -0,0 +1,72 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - 'ml/**'
8
+ pull_request:
9
+ branches:
10
+ - main
11
+
12
+ jobs:
13
+ RSpec:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ ruby-version:
19
+ - '2.5' # deprecated
20
+ - '2.6'
21
+ - '2.7'
22
+ - '3.0'
23
+ - '3.1'
24
+ include:
25
+ - ruby-version: '3.0'
26
+ coverage: 'true'
27
+ steps:
28
+ - uses: actions/checkout@v2
29
+ - name: Set up Ruby ${{ matrix.ruby-version }}
30
+ uses: ruby/setup-ruby@v1
31
+ with:
32
+ ruby-version: ${{ matrix.ruby-version }}
33
+ bundler-cache: true
34
+ - uses: amancevice/setup-code-climate@v0
35
+ name: CodeClimate setup
36
+ if: ${{ matrix.coverage == 'true' }}
37
+ with:
38
+ cc_test_reporter_id: ${{ secrets.CC_TEST_REPORTER_ID }}
39
+ - name: CodeClimate before-build
40
+ run: cc-test-reporter before-build
41
+ if: ${{ matrix.coverage == 'true' }}
42
+ continue-on-error: true
43
+ - name: Run tests
44
+ env:
45
+ COVERAGE: ${{ matrix.coverage }}
46
+ run: bundle exec rake spec
47
+ - name: CodeClimate after-build
48
+ run: cc-test-reporter after-build
49
+ if: ${{ matrix.coverage == 'true' }}
50
+ continue-on-error: true
51
+ Standard:
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@v2
55
+ - name: Set up Ruby
56
+ uses: ruby/setup-ruby@v1
57
+ with:
58
+ ruby-version: '3.0'
59
+ bundler-cache: true
60
+ - name: Run Standard
61
+ run: bundle exec rake standard
62
+ Sorbet:
63
+ runs-on: ubuntu-latest
64
+ steps:
65
+ - uses: actions/checkout@v2
66
+ - name: Set up Ruby
67
+ uses: ruby/setup-ruby@v1
68
+ with:
69
+ ruby-version: '3.0'
70
+ bundler-cache: true
71
+ - name: Run Sorbet
72
+ run: bundle exec rake sorbet
data/CHANGELOG.md CHANGED
@@ -2,7 +2,24 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
- [Commits](https://github.com/molawson/repeatable/compare/v1.0.0...main)
5
+ [Commits](https://github.com/molawson/repeatable/compare/v1.1.0...main)
6
+
7
+ ### 1.1.0 (2022-02-25)
8
+
9
+ Changes:
10
+
11
+ * Add [sorbet](https://sorbet.org) for type checking
12
+
13
+ Bug Fixes:
14
+
15
+ * Ensure `first_occurrence` is never part of `#to_h` output for `Expression::Biweekly`
16
+
17
+ Chores:
18
+
19
+ * Add support for Ruby 3.1
20
+ * Deprecate support for Ruby 2.5
21
+
22
+ [Commits](https://github.com/molawson/repeatable/compare/v1.0.0...v1.1.0)
6
23
 
7
24
  ### 1.0.0 (2021-03-25)
8
25
 
@@ -0,0 +1,128 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity
10
+ and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the
26
+ overall community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or
31
+ advances of any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email
35
+ address, without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official e-mail address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ mo@molawson.com.
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series
86
+ of actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or
93
+ permanent ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within
113
+ the community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.0, available at
119
+ https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120
+
121
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct
122
+ enforcement ladder](https://github.com/mozilla/diversity).
123
+
124
+ [homepage]: https://www.contributor-covenant.org
125
+
126
+ For answers to common questions about this code of conduct, see the FAQ at
127
+ https://www.contributor-covenant.org/faq. Translations are available at
128
+ https://www.contributor-covenant.org/translations.
data/Gemfile CHANGED
@@ -3,8 +3,10 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in repeatable.gemspec
4
4
  gemspec
5
5
 
6
+ gem "parlour"
6
7
  gem "pry", "~> 0.13"
7
8
  gem "rake", ">= 12.3.3"
9
+ gem "sorbet"
8
10
  gem "standard", "~> 1.0"
9
11
 
10
12
  group :test do
data/Gemfile.lock CHANGED
@@ -1,17 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- repeatable (1.0.0)
4
+ repeatable (1.1.0)
5
+ sorbet-runtime
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
10
  ast (2.4.2)
10
11
  coderay (1.1.3)
12
+ commander (4.6.0)
13
+ highline (~> 2.0.0)
11
14
  diff-lcs (1.4.4)
12
- docile (1.3.5)
15
+ docile (1.4.0)
16
+ highline (2.0.3)
13
17
  method_source (1.0.0)
14
18
  parallel (1.20.1)
19
+ parlour (6.0.1)
20
+ commander (~> 4.5)
21
+ parser
22
+ rainbow (~> 3.0)
23
+ sorbet-runtime (>= 0.5)
15
24
  parser (3.0.0.0)
16
25
  ast (~> 2.4.1)
17
26
  pry (0.14.0)
@@ -20,7 +29,7 @@ GEM
20
29
  rainbow (3.0.0)
21
30
  rake (13.0.3)
22
31
  regexp_parser (2.1.1)
23
- rexml (3.2.4)
32
+ rexml (3.2.5)
24
33
  rspec (3.10.0)
25
34
  rspec-core (~> 3.10.0)
26
35
  rspec-expectations (~> 3.10.0)
@@ -54,23 +63,33 @@ GEM
54
63
  simplecov-html (~> 0.11)
55
64
  simplecov_json_formatter (~> 0.1)
56
65
  simplecov-html (0.12.3)
57
- simplecov_json_formatter (0.1.2)
66
+ simplecov_json_formatter (0.1.4)
67
+ sorbet (0.5.9672)
68
+ sorbet-static (= 0.5.9672)
69
+ sorbet-runtime (0.5.6357)
70
+ sorbet-static (0.5.9672-universal-darwin-19)
71
+ sorbet-static (0.5.9672-universal-darwin-21)
72
+ sorbet-static (0.5.9672-x86_64-linux)
58
73
  standard (1.0.4)
59
74
  rubocop (= 1.11.0)
60
75
  rubocop-performance (= 1.10.1)
61
76
  unicode-display_width (2.0.0)
62
77
 
63
78
  PLATFORMS
79
+ arm64-darwin-21
80
+ linux
64
81
  x86_64-darwin-19
65
82
  x86_64-linux
66
83
 
67
84
  DEPENDENCIES
85
+ parlour
68
86
  pry (~> 0.13)
69
87
  rake (>= 12.3.3)
70
88
  repeatable!
71
89
  rspec (~> 3.0)
72
90
  simplecov (~> 0.18)
91
+ sorbet
73
92
  standard (~> 1.0)
74
93
 
75
94
  BUNDLED WITH
76
- 2.2.14
95
+ 2.3.6
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Repeatable
2
2
 
3
- [![Build Status](https://travis-ci.org/molawson/repeatable.svg?branch=main)](https://travis-ci.org/molawson/repeatable)
3
+ [![CI](https://github.com/molawson/repeatable/actions/workflows/ci.yml/badge.svg)](https://github.com/molawson/repeatable/actions/workflows/ci.yml)
4
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/73707efd5eeffd364c0d/maintainability)](https://codeclimate.com/github/molawson/repeatable/maintainability)
5
5
  [![Test Coverage](https://api.codeclimate.com/v1/badges/73707efd5eeffd364c0d/test_coverage)](https://codeclimate.com/github/molawson/repeatable/test_coverage)
6
6
 
@@ -22,10 +22,6 @@ Or install it yourself as:
22
22
 
23
23
  $ gem install repeatable
24
24
 
25
- ## Requirements
26
-
27
- Because this gem relies heavily on required keyword arguments, especially to make dumping and parsing of schedules simpler, this code will only work on **Ruby 2.2** and higher.
28
-
29
25
  ## Usage
30
26
 
31
27
  ### Building a Schedule
@@ -98,8 +94,8 @@ Repeatable::Expression::WeekdayInMonth.new(weekday: 1, count: 3)
98
94
  Repeatable::Expression::WeekdayInMonth.new(weekday: 4, count: -1)
99
95
 
100
96
  # Every other Monday, starting from December 1, 2015
101
- { biweekly: { weekday: 1, start_date: '2015-12-01' } }
102
- Repeatable::Expression::Biweekly.new(weekday: 1, start_date: Date.new(2015, 12, 1))
97
+ { biweekly: { weekday: 1, start_after: '2015-12-01' } }
98
+ Repeatable::Expression::Biweekly.new(weekday: 1, start_after: Date.new(2015, 12, 1))
103
99
 
104
100
  # The 13th of every month
105
101
  { day_in_month: { day: 13 } }
@@ -186,16 +182,39 @@ Repeatable::Schedule.new(union) == Repeatable::Schedule.new(another_union)
186
182
 
187
183
  ```
188
184
 
185
+ ## Ruby version support
186
+
187
+ Currently tested and supported:
188
+ - 2.6
189
+ - 2.7
190
+ - 3.0
191
+ - 3.1
192
+
193
+ Deprecated (currently tested but has reached EOL and will be unsupported in the next major version):
194
+ - 2.5
195
+
196
+ The supported versions will roughly track with versions that are currently maintained by the Ruby core team.
197
+
189
198
  ## Development
190
199
 
191
200
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
192
201
 
193
- 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` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
202
+ You can run the tests with `bundle exec rake`.
194
203
 
195
204
  ## Contributing
196
205
 
206
+ Bug reports and pull requests are welcome on GitHub at https://github.com/molawson/repeatable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
207
+
197
208
  1. Fork it ( https://github.com/molawson/repeatable/fork )
198
209
  2. Create your feature branch (`git checkout -b my-new-feature`)
199
210
  3. Commit your changes (`git commit -am 'Add some feature'`)
200
211
  4. Push to the branch (`git push origin my-new-feature`)
201
212
  5. Create a new Pull Request
213
+
214
+ ## License
215
+
216
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
217
+
218
+ ## Code of Conduct
219
+
220
+ Everyone interacting in the Repeatable project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/molawson/repeatable/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -5,4 +5,8 @@ RSpec::Core::RakeTask.new(:spec)
5
5
 
6
6
  require "standard/rake"
7
7
 
8
- task default: %i[spec standard]
8
+ task :sorbet do
9
+ sh %(bundle exec srb tc)
10
+ end
11
+
12
+ task default: %i[spec sorbet standard]
@@ -1,7 +1,11 @@
1
+ # typed: strict
1
2
  module Repeatable
2
3
  module Conversions
4
+ extend T::Sig
5
+
3
6
  module_function
4
7
 
8
+ sig { params(arg: Object).returns(::Date) }
5
9
  def Date(arg)
6
10
  case arg
7
11
  when Date, Time
@@ -10,7 +14,7 @@ module Repeatable
10
14
  Date.parse(arg)
11
15
  end
12
16
  rescue ArgumentError
13
- raise TypeError, "Cannot convert #{arg.inspect} to Date"
17
+ Kernel.raise TypeError, "Cannot convert #{arg.inspect} to Date"
14
18
  end
15
19
  end
16
20
  end
@@ -1,6 +1,13 @@
1
+ # typed: strict
1
2
  module Repeatable
2
3
  module Expression
3
4
  class Base
5
+ extend T::Sig
6
+ extend T::Helpers
7
+
8
+ abstract!
9
+
10
+ sig { params(other: Object).returns(T::Boolean) }
4
11
  def self.===(other)
5
12
  case other
6
13
  when Class
@@ -10,31 +17,29 @@ module Repeatable
10
17
  end
11
18
  end
12
19
 
13
- def include?(_date)
14
- fail(
15
- NotImplementedError,
16
- "Don't use Expression::Base directly. Subclasses must implement `#include?`"
17
- )
20
+ sig { abstract.params(date: ::Date).returns(T::Boolean) }
21
+ def include?(date)
18
22
  end
19
23
 
24
+ sig { returns(T::Hash[Symbol, T.any(Types::SymbolHash, T::Array[Types::SymbolHash])]) }
20
25
  def to_h
21
- fail(
22
- NotImplementedError,
23
- "Don't use Expression::Base directly. Subclasses must implement `#to_h`"
24
- )
26
+ {hash_key => hash_value}
25
27
  end
26
28
 
29
+ sig { params(other: Expression::Base).returns(Expression::Union) }
27
30
  def union(other)
28
31
  Union.new(self, other)
29
32
  end
30
33
  alias_method :+, :union
31
34
  alias_method :|, :union
32
35
 
36
+ sig { params(other: Expression::Base).returns(Expression::Intersection) }
33
37
  def intersection(other)
34
38
  Intersection.new(self, other)
35
39
  end
36
40
  alias_method :&, :intersection
37
41
 
42
+ sig { params(other: T.untyped).returns(Expression::Difference) }
38
43
  def difference(other)
39
44
  Difference.new(included: self, excluded: other)
40
45
  end
@@ -42,12 +47,17 @@ module Repeatable
42
47
 
43
48
  private
44
49
 
50
+ sig { returns(Symbol) }
45
51
  def hash_key
46
- self.class.name.split("::").last
47
- .gsub(/(?<!\b)[A-Z]/) { "_#{Regexp.last_match[0]}" }
52
+ T.must(T.must(self.class.name).split("::").last)
53
+ .gsub(/(?<!\b)[A-Z]/) { "_#{T.must(Regexp.last_match)[0]}" }
48
54
  .downcase
49
55
  .to_sym
50
56
  end
57
+
58
+ sig { abstract.returns(T.any(Types::SymbolHash, T::Array[Types::SymbolHash])) }
59
+ def hash_value
60
+ end
51
61
  end
52
62
  end
53
63
  end
@@ -1,23 +1,31 @@
1
+ # typed: strict
1
2
  module Repeatable
2
3
  module Expression
3
4
  class Biweekly < Date
5
+ sig { params(weekday: Integer, start_after: Object).void }
4
6
  def initialize(weekday:, start_after: ::Date.today)
5
7
  @weekday = weekday
6
- @start_after = Conversions::Date(start_after)
8
+ @start_after = T.let(Conversions::Date(start_after), ::Date)
9
+ @_first_occurrence = T.let(find_first_occurrence, ::Date)
7
10
  end
8
11
 
12
+ sig { override.params(date: ::Date).returns(T::Boolean) }
9
13
  def include?(date)
10
- date >= start_after && (date - first_occurrence) % 14 == 0
14
+ date >= start_after && (date - _first_occurrence) % 14 == 0
11
15
  end
12
16
 
13
17
  private
14
18
 
15
- attr_reader :weekday, :start_after
19
+ sig { returns(Integer) }
20
+ attr_reader :weekday
16
21
 
17
- def first_occurrence
18
- @first_occurrence ||= find_first_occurrence
19
- end
22
+ sig { returns(::Date) }
23
+ attr_reader :start_after
24
+
25
+ sig { returns(::Date) }
26
+ attr_reader :_first_occurrence
20
27
 
28
+ sig { returns(::Date) }
21
29
  def find_first_occurrence
22
30
  days_away = weekday - start_after.wday
23
31
  days_away += 7 if days_away <= 0
@@ -1,29 +1,33 @@
1
+ # typed: strict
1
2
  module Repeatable
2
3
  module Expression
3
4
  class Date < Base
4
- def to_h
5
- Hash[hash_key, attributes]
6
- end
5
+ abstract!
7
6
 
7
+ sig { params(other: Object).returns(T::Boolean) }
8
8
  def ==(other)
9
9
  other.is_a?(self.class) && attributes == other.attributes
10
10
  end
11
-
12
11
  alias_method :eql?, :==
13
12
 
13
+ sig { returns(Integer) }
14
14
  def hash
15
15
  [attributes.values, self.class.name].hash
16
16
  end
17
17
 
18
18
  protected
19
19
 
20
+ sig { returns(Types::SymbolHash) }
20
21
  def attributes
21
22
  instance_variables.each_with_object({}) do |name, hash|
22
- key = name.to_s.gsub(/^@/, "").to_sym
23
- hash[key] = normalize_attribute_value(instance_variable_get(name))
23
+ key = name.to_s.gsub(/^@/, "")
24
+ next if key.start_with?("_")
25
+ hash[key.to_sym] = normalize_attribute_value(instance_variable_get(name))
24
26
  end
25
27
  end
28
+ alias_method :hash_value, :attributes
26
29
 
30
+ sig { params(value: BasicObject).returns(T.untyped) }
27
31
  def normalize_attribute_value(value)
28
32
  case value
29
33
  when ::Date
@@ -1,12 +1,15 @@
1
+ # typed: strict
1
2
  module Repeatable
2
3
  module Expression
3
4
  class DayInMonth < Date
4
5
  include LastDateOfMonth
5
6
 
7
+ sig { params(day: Integer).void }
6
8
  def initialize(day:)
7
9
  @day = day
8
10
  end
9
11
 
12
+ sig { override.params(date: ::Date).returns(T::Boolean) }
10
13
  def include?(date)
11
14
  if day < 0
12
15
  date - last_date_of_month(date) - 1 == day
@@ -17,6 +20,7 @@ module Repeatable
17
20
 
18
21
  private
19
22
 
23
+ sig { returns(Integer) }
20
24
  attr_reader :day
21
25
  end
22
26
  end
@@ -1,19 +1,19 @@
1
+ # typed: strict
1
2
  module Repeatable
2
3
  module Expression
3
4
  class Difference < Base
5
+ sig { params(included: Expression::Base, excluded: Expression::Base).void }
4
6
  def initialize(included:, excluded:)
5
7
  @included = included
6
8
  @excluded = excluded
7
9
  end
8
10
 
11
+ sig { override.params(date: ::Date).returns(T::Boolean) }
9
12
  def include?(date)
10
13
  included.include?(date) && !excluded.include?(date)
11
14
  end
12
15
 
13
- def to_h
14
- Hash[hash_key, {included: included.to_h, excluded: excluded.to_h}]
15
- end
16
-
16
+ sig { params(other: Object).returns(T::Boolean) }
17
17
  def ==(other)
18
18
  other.is_a?(self.class) &&
19
19
  included == other.included &&
@@ -22,7 +22,16 @@ module Repeatable
22
22
 
23
23
  protected
24
24
 
25
- attr_reader :included, :excluded
25
+ sig { returns(Expression::Base) }
26
+ attr_reader :included
27
+
28
+ sig { returns(Expression::Base) }
29
+ attr_reader :excluded
30
+
31
+ sig { override.returns(Types::SymbolHash) }
32
+ def hash_value
33
+ {included: included.to_h, excluded: excluded.to_h}
34
+ end
26
35
  end
27
36
  end
28
37
  end
@@ -1,16 +1,20 @@
1
+ # typed: strict
1
2
  module Repeatable
2
3
  module Expression
3
4
  class ExactDate < Date
5
+ sig { params(date: Object).void }
4
6
  def initialize(date:)
5
- @date = Conversions::Date(date)
7
+ @date = T.let(Conversions::Date(date), ::Date)
6
8
  end
7
9
 
10
+ sig { override.params(other_date: ::Date).returns(T::Boolean) }
8
11
  def include?(other_date)
9
12
  date == other_date
10
13
  end
11
14
 
12
15
  private
13
16
 
17
+ sig { returns(::Date) }
14
18
  attr_reader :date
15
19
  end
16
20
  end