repeatable 0.5.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/.git-blame-ignore-revs +13 -0
  3. data/.github/workflows/ci.yml +72 -0
  4. data/.gitignore +0 -1
  5. data/.rspec +0 -1
  6. data/.standard.yml +3 -0
  7. data/CHANGELOG.md +58 -3
  8. data/CODE_OF_CONDUCT.md +128 -0
  9. data/Gemfile +11 -3
  10. data/Gemfile.lock +95 -0
  11. data/README.md +49 -9
  12. data/Rakefile +9 -3
  13. data/lib/repeatable/conversions.rb +5 -1
  14. data/lib/repeatable/expression/base.rb +37 -11
  15. data/lib/repeatable/expression/biweekly.rb +14 -6
  16. data/lib/repeatable/expression/date.rb +11 -8
  17. data/lib/repeatable/expression/day_in_month.rb +11 -1
  18. data/lib/repeatable/expression/difference.rb +37 -0
  19. data/lib/repeatable/expression/exact_date.rb +21 -0
  20. data/lib/repeatable/expression/intersection.rb +9 -0
  21. data/lib/repeatable/expression/range_in_year.rb +39 -9
  22. data/lib/repeatable/expression/set.rb +15 -7
  23. data/lib/repeatable/expression/union.rb +9 -0
  24. data/lib/repeatable/expression/weekday.rb +4 -0
  25. data/lib/repeatable/expression/weekday_in_month.rb +35 -4
  26. data/lib/repeatable/expression.rb +1 -0
  27. data/lib/repeatable/last_date_of_month.rb +11 -0
  28. data/lib/repeatable/parse_error.rb +1 -0
  29. data/lib/repeatable/parser.rb +14 -2
  30. data/lib/repeatable/schedule.rb +23 -9
  31. data/lib/repeatable/types.rb +6 -0
  32. data/lib/repeatable/version.rb +2 -1
  33. data/lib/repeatable.rb +25 -19
  34. data/rbi/repeatable.rbi +310 -0
  35. data/repeatable.gemspec +15 -15
  36. data/sorbet/config +3 -0
  37. data/sorbet/rbi/gems/ast.rbi +49 -0
  38. data/sorbet/rbi/gems/coderay.rbi +285 -0
  39. data/sorbet/rbi/gems/commander.rbi +197 -0
  40. data/sorbet/rbi/gems/docile.rbi +36 -0
  41. data/sorbet/rbi/gems/highline.rbi +577 -0
  42. data/sorbet/rbi/gems/method_source.rbi +64 -0
  43. data/sorbet/rbi/gems/parallel.rbi +83 -0
  44. data/sorbet/rbi/gems/parlour.rbi +840 -0
  45. data/sorbet/rbi/gems/parser.rbi +1950 -0
  46. data/sorbet/rbi/gems/pry.rbi +1898 -0
  47. data/sorbet/rbi/gems/rainbow.rbi +118 -0
  48. data/sorbet/rbi/gems/rake.rbi +646 -0
  49. data/sorbet/rbi/gems/regexp_parser.rbi +926 -0
  50. data/sorbet/rbi/gems/repeatable.rbi +13 -0
  51. data/sorbet/rbi/gems/rexml.rbi +583 -0
  52. data/sorbet/rbi/gems/rspec-core.rbi +1919 -0
  53. data/sorbet/rbi/gems/rspec-expectations.rbi +1150 -0
  54. data/sorbet/rbi/gems/rspec-mocks.rbi +1100 -0
  55. data/sorbet/rbi/gems/rspec-support.rbi +280 -0
  56. data/sorbet/rbi/gems/rspec.rbi +15 -0
  57. data/sorbet/rbi/gems/rubocop-ast.rbi +1356 -0
  58. data/sorbet/rbi/gems/rubocop-performance.rbi +487 -0
  59. data/sorbet/rbi/gems/rubocop.rbi +7923 -0
  60. data/sorbet/rbi/gems/ruby-progressbar.rbi +304 -0
  61. data/sorbet/rbi/gems/simplecov-html.rbi +35 -0
  62. data/sorbet/rbi/gems/simplecov.rbi +419 -0
  63. data/sorbet/rbi/gems/simplecov_json_formatter.rbi +47 -0
  64. data/sorbet/rbi/gems/standard.rbi +130 -0
  65. data/sorbet/rbi/gems/unicode-display_width.rbi +20 -0
  66. data/sorbet/rbi/hidden-definitions/errors.txt +4273 -0
  67. data/sorbet/rbi/hidden-definitions/hidden.rbi +9013 -0
  68. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +276 -0
  69. data/sorbet/rbi/sorbet-typed/lib/rake/all/rake.rbi +645 -0
  70. data/sorbet/rbi/sorbet-typed/lib/rspec-core/all/rspec-core.rbi +24 -0
  71. data/sorbet/rbi/sorbet-typed/lib/rubocop/>=1.8/rubocop.rbi +12 -0
  72. data/sorbet/rbi/sorbet-typed/lib/rubocop-performance/~>1.6/rubocop-performance.rbi +149 -0
  73. metadata +62 -45
  74. data/.travis.yml +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7810dbdca8bf11e8e30101cc1bd22afbcaf698a0
4
- data.tar.gz: e1408eb13dbf184b1115d90ae814db6582b0a640
2
+ SHA256:
3
+ metadata.gz: 71a9ecf64d973e0e8298628e5ab19ab4025c9c3281a357efaae330f42c9767a0
4
+ data.tar.gz: 190a230b24d32f0df2f6565d35c2da1867d3ae87c82d0e00d98ab16b53baa46c
5
5
  SHA512:
6
- metadata.gz: ebd63d839565ebe7971e5566462dad20d851a9a110eee829d1f53ec8868559fa6b46f14c8528e067827daa95da6e9929a479ca852d69e54884b4a6c071a3b139
7
- data.tar.gz: b623c8e4040e6a8276f86c7b71078019fb830dd2ff88b0d5e784618580350cd5b8c51e45ce3846126765a97a1c448ea3e9d9d1f2261ded030cb7ad7ee195dcab
6
+ metadata.gz: 8335d2132cd0fc2cdf5d991431bafcec688819b9b2df45090afcdb05886f74632ebb262ae6f014911aa300589a5f1a776d2a5325adb39569824af306cd94bbfc
7
+ data.tar.gz: 26a637424b6a64a3a1a6b6db0ed8fb6c0e1ece5040b15499182fd3e45cdb3bf7298634620d1f3284af1a47f00655f7bac7212bea369fbc5f287e154fc3ce45cf
@@ -0,0 +1,13 @@
1
+ # These commits should be ignored by git-blame, making it much easier to step through the history
2
+ # of a line without hitting formatting commits (esp. large, update-all-the-files sorts of commits).
3
+ #
4
+ # For git-blame to ignore these revisions, you'll need to tell it about this file.
5
+ #
6
+ # You can either pass it to the command each time:
7
+ # git blame --ignore-rev-file=.git-blame-ignore-revs -L 12,14 lib/repeatable/conversions.rb
8
+ #
9
+ # Or you can set it as a config value, which will be used by all git-blame commands henceforth:
10
+ # git config blame.ignoreRevsFile .git-blame-ignore-revs
11
+
12
+ # Initial style fix after introducing Standard
13
+ 80611e9be4fe12e6b9bdb937d37902c12ac3a53b
@@ -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/.gitignore CHANGED
@@ -1,7 +1,6 @@
1
1
  /*.gem
2
2
  /.bundle/
3
3
  /.yardoc
4
- /Gemfile.lock
5
4
  /_yardoc/
6
5
  /coverage/
7
6
  /doc/
data/.rspec CHANGED
@@ -1,2 +1 @@
1
- --format documentation
2
1
  --color
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see: https://github.com/testdouble/standard
2
+
3
+ default_ignores: true
data/CHANGELOG.md CHANGED
@@ -2,7 +2,59 @@
2
2
 
3
3
  ### Unreleased
4
4
 
5
- [Commits](https://github.com/molawson/repeatable/compare/v0.5.0...master)
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)
23
+
24
+ ### 1.0.0 (2021-03-25)
25
+
26
+ Breaking Changes:
27
+
28
+ * Change `Expression::DayInMonth` to take negative numbers instead of special `:last` symbol (or string)
29
+ * Stop including `Conversions` in top level namespace (must now be accessed via `Repeatable::Conversions::Date()` or by calling `include Repeatable::Conversions` in whatever namespace(s) it's needed)
30
+ * Require Ruby 2.5.0 or greater
31
+
32
+ Changes:
33
+
34
+ * Flatten nested `Expression::Union` elements during initialization ([@cmoel][])
35
+ * Flatten nested `Expression::Intersection` elements during initialization
36
+
37
+ Chores:
38
+
39
+ * Add support for Ruby 2.5, 2.6, 2.7, 3.0
40
+ * Introduce [standard](https://github.com/testdouble/standard) for code formatting
41
+
42
+ [Commits](https://github.com/molawson/repeatable/compare/v0.6.0...v1.0.0)
43
+
44
+
45
+ ### 0.6.0 (2017-05-04)
46
+
47
+ Features:
48
+
49
+ * Add `Expression::Difference` for set differences between 2 schedules ([@danott][])
50
+ * Allow `Expression::DayInMonth` to take `:last` (or `'last'`) for its `day:` argument ([@PatrickLerner][])
51
+ * Allow `Expression::WeekdayInMonth` to take negative `count` argument for last, second-to-last, etc. of a given weekday ([@danielma][])
52
+
53
+ Bug Fixes:
54
+
55
+ * Fix `Expression::RangeInYear` to properly handle using `start_day` and `end_day` when `start_month == end_month` ([@danielma][])
56
+
57
+ [Commits](https://github.com/molawson/repeatable/compare/v0.5.0...v0.6.0)
6
58
 
7
59
  ### 0.5.0 (2016-01-27)
8
60
 
@@ -38,7 +90,7 @@ Features:
38
90
  * Add `ParseError` class for better error handling
39
91
  * Extract `Parser` class from `Schedule`
40
92
 
41
- Bugfixes:
93
+ Bug Fixes:
42
94
 
43
95
  * Enable `Schedule` to take a hash with string keys
44
96
 
@@ -51,7 +103,7 @@ Features:
51
103
  * Add `Schedule#to_h` and `Expression#to_h` methods
52
104
  * Enable building a `Schedule` from composed `Expression` objects
53
105
 
54
- Bugfixes:
106
+ Bug Fixes:
55
107
 
56
108
  * Fix default case equality for `Expression::Base` to work with classes and instances
57
109
 
@@ -65,3 +117,6 @@ Initial Release
65
117
 
66
118
 
67
119
  [@danott]: https://github.com/danott
120
+ [@PatrickLerner]: https://github.com/PatrickLerner
121
+ [@danielma]: https://github.com/danielma
122
+ [@cmoel]: https://github.com/cmoel
@@ -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
@@ -1,7 +1,15 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in repeatable.gemspec
4
4
  gemspec
5
5
 
6
- gem 'pry'
7
- gem 'codeclimate-test-reporter', group: :test, require: nil
6
+ gem "parlour"
7
+ gem "pry", "~> 0.13"
8
+ gem "rake", ">= 12.3.3"
9
+ gem "sorbet"
10
+ gem "standard", "~> 1.0"
11
+
12
+ group :test do
13
+ gem "rspec", "~> 3.0"
14
+ gem "simplecov", "~> 0.18"
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,95 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ repeatable (1.1.0)
5
+ sorbet-runtime
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ coderay (1.1.3)
12
+ commander (4.6.0)
13
+ highline (~> 2.0.0)
14
+ diff-lcs (1.4.4)
15
+ docile (1.4.0)
16
+ highline (2.0.3)
17
+ method_source (1.0.0)
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)
24
+ parser (3.0.0.0)
25
+ ast (~> 2.4.1)
26
+ pry (0.14.0)
27
+ coderay (~> 1.1)
28
+ method_source (~> 1.0)
29
+ rainbow (3.0.0)
30
+ rake (13.0.3)
31
+ regexp_parser (2.1.1)
32
+ rexml (3.2.5)
33
+ rspec (3.10.0)
34
+ rspec-core (~> 3.10.0)
35
+ rspec-expectations (~> 3.10.0)
36
+ rspec-mocks (~> 3.10.0)
37
+ rspec-core (3.10.1)
38
+ rspec-support (~> 3.10.0)
39
+ rspec-expectations (3.10.1)
40
+ diff-lcs (>= 1.2.0, < 2.0)
41
+ rspec-support (~> 3.10.0)
42
+ rspec-mocks (3.10.2)
43
+ diff-lcs (>= 1.2.0, < 2.0)
44
+ rspec-support (~> 3.10.0)
45
+ rspec-support (3.10.2)
46
+ rubocop (1.11.0)
47
+ parallel (~> 1.10)
48
+ parser (>= 3.0.0.0)
49
+ rainbow (>= 2.2.2, < 4.0)
50
+ regexp_parser (>= 1.8, < 3.0)
51
+ rexml
52
+ rubocop-ast (>= 1.2.0, < 2.0)
53
+ ruby-progressbar (~> 1.7)
54
+ unicode-display_width (>= 1.4.0, < 3.0)
55
+ rubocop-ast (1.4.1)
56
+ parser (>= 2.7.1.5)
57
+ rubocop-performance (1.10.1)
58
+ rubocop (>= 0.90.0, < 2.0)
59
+ rubocop-ast (>= 0.4.0)
60
+ ruby-progressbar (1.11.0)
61
+ simplecov (0.21.2)
62
+ docile (~> 1.1)
63
+ simplecov-html (~> 0.11)
64
+ simplecov_json_formatter (~> 0.1)
65
+ simplecov-html (0.12.3)
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)
73
+ standard (1.0.4)
74
+ rubocop (= 1.11.0)
75
+ rubocop-performance (= 1.10.1)
76
+ unicode-display_width (2.0.0)
77
+
78
+ PLATFORMS
79
+ arm64-darwin-21
80
+ linux
81
+ x86_64-darwin-19
82
+ x86_64-linux
83
+
84
+ DEPENDENCIES
85
+ parlour
86
+ pry (~> 0.13)
87
+ rake (>= 12.3.3)
88
+ repeatable!
89
+ rspec (~> 3.0)
90
+ simplecov (~> 0.18)
91
+ sorbet
92
+ standard (~> 1.0)
93
+
94
+ BUNDLED WITH
95
+ 2.3.6
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Repeatable
2
2
 
3
- [![Build Status](https://img.shields.io/travis/molawson/repeatable.svg)](https://travis-ci.org/molawson/repeatable)
4
- [![Code Climate](https://img.shields.io/codeclimate/github/molawson/repeatable.svg)](https://codeclimate.com/github/molawson/repeatable)
5
- [![Code Climate Coverage](https://img.shields.io/codeclimate/coverage/github/molawson/repeatable.svg)](https://codeclimate.com/github/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
+ [![Maintainability](https://api.codeclimate.com/v1/badges/73707efd5eeffd364c0d/maintainability)](https://codeclimate.com/github/molawson/repeatable/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/73707efd5eeffd364c0d/test_coverage)](https://codeclimate.com/github/molawson/repeatable/test_coverage)
6
6
 
7
7
  Ruby implementation of Martin Fowler's [Recurring Events for Calendars](http://martinfowler.com/apsupp/recurring.pdf) paper.
8
8
 
@@ -33,9 +33,9 @@ You can create a schedule in one of two ways.
33
33
  Instantiate and compose each of the `Repeatable::Expression` objects manually.
34
34
 
35
35
  ```ruby
36
- second_monday = Repeatabe::Expression::WeekdayInMonth.new(weekday: 1, count: 2)
36
+ second_monday = Repeatable::Expression::WeekdayInMonth.new(weekday: 1, count: 2)
37
37
  oct_thru_dec = Repeatable::Expression::RangeInYear.new(start_month: 10, end_month: 12)
38
- intersection = Repeatable::Expresson::Intersection.new(second_monday, oct_thru_dec)
38
+ intersection = Repeatable::Expression::Intersection.new(second_monday, oct_thru_dec)
39
39
 
40
40
  schedule = Repeatable::Schedule.new(intersection)
41
41
  ```
@@ -49,7 +49,8 @@ Or describe the same structure with a `Hash`, and the gem will handle instantiat
49
49
  arg = {
50
50
  intersection: [
51
51
  { weekday_in_month: { weekday: 1, count: 2 } },
52
- { range_in_year: { start_month: 10, end_month: 12 } }
52
+ { range_in_year: { start_month: 10, end_month: 12 } },
53
+ { exact_date: { date: "2015-08-01" } }
53
54
  ]
54
55
  }
55
56
 
@@ -73,6 +74,10 @@ Repeatable::Expression::Union.new(expressions)
73
74
  { intersection: [] }
74
75
  Repeatable::Expression::Intersection.new(expressions)
75
76
 
77
+ # Date is part of the first set (`included`) but not part of the second set (`excluded`)
78
+ { difference: { included: expression, excluded: another_expression } }
79
+ Repeatable::Expression::Difference.new(included: expression, excluded: another_expression)
80
+
76
81
 
77
82
  # DATES
78
83
 
@@ -84,14 +89,22 @@ Repeatable::Expression::Weekday.new(weekday: 0)
84
89
  { weekday_in_month: { weekday: 1, count: 3 } }
85
90
  Repeatable::Expression::WeekdayInMonth.new(weekday: 1, count: 3)
86
91
 
92
+ # The last Thursday of every month
93
+ { weekday_in_month: { weekday: 4, count: -1 } }
94
+ Repeatable::Expression::WeekdayInMonth.new(weekday: 4, count: -1)
95
+
87
96
  # Every other Monday, starting from December 1, 2015
88
- { biweekly: { weekday: 1, start_date: '2015-12-01' } }
89
- 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))
90
99
 
91
100
  # The 13th of every month
92
101
  { day_in_month: { day: 13 } }
93
102
  Repeatable::Expression::DayInMonth.new(day: 13)
94
103
 
104
+ # The last day of every month
105
+ { day_in_month: { day: -1 } }
106
+ Repeatable::Expression::DayInMonth.new(day: -1)
107
+
95
108
  # All days in October
96
109
  { range_in_year: { start_month: 10 } }
97
110
  Repeatable::Expression::RangeInYear.new(start_month: 10)
@@ -103,6 +116,10 @@ Repeatable::Expression::RangeInYear.new(start_month: 10, end_month: 12)
103
116
  # All days from October 1 through December 20
104
117
  { range_in_year: { start_month: 10, end_month: 12, start_day: 1, end_day: 20 } }
105
118
  Repeatable::Expression::RangeInYear.new(start_month: 10, end_month: 12, start_day: 1, end_day: 20)
119
+
120
+ # only December 21, 2012
121
+ { exact_date: { date: '2012-12-21' } }
122
+ Repeatable::Expression::ExactDate.new(date: Date.new(2012, 12, 21)
106
123
  ```
107
124
 
108
125
  #### Schedule Errors
@@ -165,16 +182,39 @@ Repeatable::Schedule.new(union) == Repeatable::Schedule.new(another_union)
165
182
 
166
183
  ```
167
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
+
168
198
  ## Development
169
199
 
170
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.
171
201
 
172
- 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`.
173
203
 
174
204
  ## Contributing
175
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
+
176
208
  1. Fork it ( https://github.com/molawson/repeatable/fork )
177
209
  2. Create your feature branch (`git checkout -b my-new-feature`)
178
210
  3. Commit your changes (`git commit -am 'Add some feature'`)
179
211
  4. Push to the branch (`git push origin my-new-feature`)
180
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
@@ -1,6 +1,12 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task default: :spec
6
+ require "standard/rake"
7
+
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,28 +17,47 @@ 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}
27
+ end
28
+
29
+ sig { params(other: Expression::Base).returns(Expression::Union) }
30
+ def union(other)
31
+ Union.new(self, other)
32
+ end
33
+ alias_method :+, :union
34
+ alias_method :|, :union
35
+
36
+ sig { params(other: Expression::Base).returns(Expression::Intersection) }
37
+ def intersection(other)
38
+ Intersection.new(self, other)
39
+ end
40
+ alias_method :&, :intersection
41
+
42
+ sig { params(other: T.untyped).returns(Expression::Difference) }
43
+ def difference(other)
44
+ Difference.new(included: self, excluded: other)
25
45
  end
46
+ alias_method :-, :difference
26
47
 
27
48
  private
28
49
 
50
+ sig { returns(Symbol) }
29
51
  def hash_key
30
- self.class.name.split('::').last
31
- .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]}" }
32
54
  .downcase
33
55
  .to_sym
34
56
  end
57
+
58
+ sig { abstract.returns(T.any(Types::SymbolHash, T::Array[Types::SymbolHash])) }
59
+ def hash_value
60
+ end
35
61
  end
36
62
  end
37
63
  end