remind_me 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +24 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +61 -0
- data/LICENSE.txt +21 -0
- data/README.md +189 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/remind_me/bail_out.rb +11 -0
- data/lib/remind_me/railtie.rb +17 -0
- data/lib/remind_me/reminder/base_reminder.rb +42 -0
- data/lib/remind_me/reminder/gem_version_reminder.rb +54 -0
- data/lib/remind_me/reminder/generator.rb +72 -0
- data/lib/remind_me/reminder/invalid_reminder.rb +14 -0
- data/lib/remind_me/reminder/missing_gem_reminder.rb +20 -0
- data/lib/remind_me/reminder/ruby_version_reminder.rb +43 -0
- data/lib/remind_me/runner.rb +69 -0
- data/lib/remind_me/tasks/remind_me.rake +8 -0
- data/lib/remind_me/utils/hash_ast_manipulations.rb +122 -0
- data/lib/remind_me/utils/logger.rb +36 -0
- data/lib/remind_me/utils/result_printer.rb +38 -0
- data/lib/remind_me/utils/versions.rb +37 -0
- data/lib/remind_me/version.rb +5 -0
- data/lib/remind_me.rb +7 -0
- data/remind_me.gemspec +36 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8ea5c318524b2dd5b11cb96b2ff7870e469a38bab9c895f3b62eb86c2e229c57
|
4
|
+
data.tar.gz: ac8c013b669d56012c1a3cbac04eb7f363dfc0c1e3a22540800ef6dd7e938acf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f6a200f413d221cd864a1b33ea2b890fd04c4f4573575c3c48500311c02a148fc7726e50d7c2929c56071e688d609b00fdcd651517e2c5136bb8989d9cb1528b
|
7
|
+
data.tar.gz: c192d2ea83b3f41e003b769d662cb5c3c1df9ee0d03404370f5033f0f8825417c2596755997ae522bc50168b2e5e4f22f4a6ea788def5e65ca401f9c95cc5a20
|
@@ -0,0 +1,27 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
strategy:
|
9
|
+
fail-fast: false
|
10
|
+
matrix:
|
11
|
+
ruby: [ "2.3.7", "2.4.10", "2.5.9", "2.6.8", "2.7.4", "3.0.2", "jruby-9.2" ]
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v2
|
14
|
+
- name: Set up Ruby
|
15
|
+
uses: ruby/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: ${{ matrix.ruby }}
|
18
|
+
- name: Bundle install
|
19
|
+
run: |
|
20
|
+
gem install bundler
|
21
|
+
bundle install --jobs=3
|
22
|
+
- name: Run tests (MRI)
|
23
|
+
run: bundle exec rake spec
|
24
|
+
if: "!startsWith(matrix.ruby, 'jruby')"
|
25
|
+
- name: Run tests (JRuby)
|
26
|
+
run: JRUBY_OPTS=--debug bundle exec rake spec
|
27
|
+
if: startsWith(matrix.ruby, 'jruby')
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Style/StringLiterals:
|
2
|
+
Enabled: true
|
3
|
+
EnforcedStyle: single_quotes
|
4
|
+
|
5
|
+
Style/StringLiteralsInInterpolation:
|
6
|
+
Enabled: true
|
7
|
+
EnforcedStyle: double_quotes
|
8
|
+
|
9
|
+
Style/Documentation:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Layout/LineLength:
|
13
|
+
Max: 120
|
14
|
+
|
15
|
+
Metrics/MethodLength:
|
16
|
+
Max: 14
|
17
|
+
|
18
|
+
AllCops:
|
19
|
+
Exclude:
|
20
|
+
- 'spec/**/*'
|
21
|
+
|
22
|
+
Metrics/BlockLength:
|
23
|
+
Exclude:
|
24
|
+
- 'spec/remind_me/reminders/**/*'
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at nikola@deversity.net. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
remind_me (0.2.0)
|
5
|
+
parser (~> 3.0.2.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ast (2.4.2)
|
11
|
+
diff-lcs (1.4.4)
|
12
|
+
docile (1.3.5)
|
13
|
+
jaro_winkler (1.5.4)
|
14
|
+
json (2.6.1)
|
15
|
+
parallel (1.19.2)
|
16
|
+
parser (3.0.2.0)
|
17
|
+
ast (~> 2.4.1)
|
18
|
+
rainbow (3.0.0)
|
19
|
+
rake (13.0.6)
|
20
|
+
rexml (3.2.5)
|
21
|
+
rspec (3.10.0)
|
22
|
+
rspec-core (~> 3.10.0)
|
23
|
+
rspec-expectations (~> 3.10.0)
|
24
|
+
rspec-mocks (~> 3.10.0)
|
25
|
+
rspec-core (3.10.1)
|
26
|
+
rspec-support (~> 3.10.0)
|
27
|
+
rspec-expectations (3.10.1)
|
28
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
29
|
+
rspec-support (~> 3.10.0)
|
30
|
+
rspec-mocks (3.10.2)
|
31
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
+
rspec-support (~> 3.10.0)
|
33
|
+
rspec-support (3.10.3)
|
34
|
+
rubocop (0.81.0)
|
35
|
+
jaro_winkler (~> 1.5.1)
|
36
|
+
parallel (~> 1.10)
|
37
|
+
parser (>= 2.7.0.1)
|
38
|
+
rainbow (>= 2.2.2, < 4.0)
|
39
|
+
rexml
|
40
|
+
ruby-progressbar (~> 1.7)
|
41
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
42
|
+
ruby-progressbar (1.11.0)
|
43
|
+
simplecov (0.17.1)
|
44
|
+
docile (~> 1.1)
|
45
|
+
json (>= 1.8, < 3)
|
46
|
+
simplecov-html (~> 0.10.0)
|
47
|
+
simplecov-html (0.10.2)
|
48
|
+
unicode-display_width (1.8.0)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
rake
|
55
|
+
remind_me!
|
56
|
+
rspec
|
57
|
+
rubocop
|
58
|
+
simplecov
|
59
|
+
|
60
|
+
BUNDLED WITH
|
61
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 nikola
|
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,189 @@
|
|
1
|
+
# Remind Me
|
2
|
+
[![Tests](https://github.com/nikola-maric/remind_me/workflows/Tests/badge.svg?branch=master)](https://github.com/nikola-maric/remind_me/actions?query=workflow%3ATests+branch%3Amaster)
|
3
|
+
|
4
|
+
This gem's main purpose is to scan a file or directory for specific comments in the code. Comments are
|
5
|
+
specified as a hash with `REMIND_ME:` (with a colon) prefixed to it, for example:
|
6
|
+
`# REMIND_ME: { gem: 'rails', version: '5.2', condition: :gte, message: 'Remove this method once we switch to Rails 5.2'}`.
|
7
|
+
|
8
|
+
Idea is that we often get into situations like this: we create some functionality, but know some
|
9
|
+
limitations or that we should revisit this part of the code once conditions change, like:
|
10
|
+
- _"We should use `Comparable#clamp` once we upgrade to Ruby 2.4"_
|
11
|
+
- _"This patch won't be needed once we stop using `activerecord-multitenant` gem"_
|
12
|
+
- _"Remove this patch if #203 is merged into main after we bump version of `ruby-debug-ide`"_
|
13
|
+
- etc.
|
14
|
+
|
15
|
+
`TODO`-s are ok,
|
16
|
+
but if we are working with large codebase with lots of `TODO`-s sprinkled all over the place
|
17
|
+
we will probably never remember to revisit those parts of the code, and that TODO will stay there for
|
18
|
+
a looong time.
|
19
|
+
|
20
|
+
One of the options is to use something like [todo_or_die](https://github.com/searls/todo_or_die) which will work for these
|
21
|
+
use cases, but in my opinion is too invasive: you need to add code to your own code. Approach here is that you can define
|
22
|
+
a script or a rake task that will do this check for you, when you want it, for example in a CI environment.
|
23
|
+
Other than that, these are just comments and won't affect your running code in any way.
|
24
|
+
|
25
|
+
If conditions are met for at least one reminder, script will print all of them out and abort
|
26
|
+
(stopping your CI pipeline, for example). You should then revisit those parts of the code and either do some housekeeping
|
27
|
+
on the code, or remove/change reminder.
|
28
|
+
|
29
|
+
## Installation
|
30
|
+
|
31
|
+
Add this line to your application's Gemfile:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
gem 'remind_me'
|
35
|
+
```
|
36
|
+
|
37
|
+
And then execute:
|
38
|
+
|
39
|
+
$ bundle install
|
40
|
+
|
41
|
+
Or install it yourself as:
|
42
|
+
|
43
|
+
$ gem install remind_me
|
44
|
+
|
45
|
+
## Usage
|
46
|
+
|
47
|
+
Script supports both single and multiline comments, so any of these will work:
|
48
|
+
```ruby
|
49
|
+
# REMIND_ME: { gem: 'rails', version: '6', condition: :gte, message: 'Check this once rails 6 is available'}
|
50
|
+
def monkey_patch_x
|
51
|
+
1 + 1
|
52
|
+
end
|
53
|
+
```
|
54
|
+
```ruby
|
55
|
+
=begin
|
56
|
+
some other comments
|
57
|
+
REMIND_ME: { gem: 'rails', version: '6', condition: :gte, message: 'Check this once rails 6 is available'}
|
58
|
+
more comments here
|
59
|
+
=end
|
60
|
+
def monkey_patch_x
|
61
|
+
1 + 1
|
62
|
+
end
|
63
|
+
```
|
64
|
+
```ruby
|
65
|
+
|
66
|
+
def monkey_patch_x
|
67
|
+
1 + 1 # REMIND_ME: { ruby_version: '2.4', condition: :gte, message: 'Check this once Ruby is >= 2.4'}
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
**Important note**: in order to find and parse those comments properly, there are some limitations on how comments are used:
|
72
|
+
- Entire `REMIND_ME: {}` comment needs to be on a single line, don't break it into multiple lines.
|
73
|
+
- Thing after `REMIND_ME:` needs to have ruby hash structure
|
74
|
+
- Both keys and values should be strings, symbols or strings/symbols, depending on reminder (for example, keys can
|
75
|
+
be given as symbols, but for `version` values strings are only allowed)
|
76
|
+
|
77
|
+
Each reminder type has a specific key that it "targets", for example `ruby_version`. If comment is found that does not
|
78
|
+
match any reminder, we will print out error message specifying that + location where that reminder is found.
|
79
|
+
|
80
|
+
Same will happen if reminder is not a hash, or does not have required structure (
|
81
|
+
`REMIND_ME: { gem => ->() {exit(1)}.call }`), is unparsable (`REMIND_ME: {{ bla }`) or does not apply
|
82
|
+
to any known reminder processor.
|
83
|
+
|
84
|
+
### Supported reminder types
|
85
|
+
|
86
|
+
**GemVersionReminder**
|
87
|
+
|
88
|
+
Has following structure:
|
89
|
+
`REMIND_ME: { gem: 'rails', version: '6', condition: :gte, message: 'Check this once rails 6 is available'}`.
|
90
|
+
It targets all comment hash-es that have `gem/'gem'` in them as a key.
|
91
|
+
It will look at currently installed gems and compare that version to target version specified in comment.
|
92
|
+
|
93
|
+
If version is omitted, we will only check if gem is installed or not (can be used to trigger
|
94
|
+
reminder when we add new gem).
|
95
|
+
|
96
|
+
If condition is omitted, it will default to `eq`.
|
97
|
+
|
98
|
+
if message is omitted, it will default to `'Condition met!'`
|
99
|
+
|
100
|
+
**MissingGemReminder**
|
101
|
+
|
102
|
+
Has following structure:
|
103
|
+
`REMIND_ME: { missing_gem: 'thor', message: 'Check this once we remove 'thor' gem'}`.
|
104
|
+
It targets all comment hash-es that have `missing_gem/'missing_gem'` in them as a key.
|
105
|
+
It will look at currently installed gems and check to see if gem is installed, and will be triggered
|
106
|
+
if its not.
|
107
|
+
|
108
|
+
if message is omitted, it will default to `'Condition met!'`
|
109
|
+
|
110
|
+
**RubyVersionReminder**
|
111
|
+
|
112
|
+
Has following structure:
|
113
|
+
`REMIND_ME: { ruby_version: '3', condition: :gte, message: 'Check this once we start using Ruby 3.0+'}`.
|
114
|
+
It targets all comment hash-es that have `ruby_version/'ruby_version'` in them as a key.
|
115
|
+
It will look at currently used ruby version and compare that version to target version specified in comment.
|
116
|
+
|
117
|
+
If condition is omitted, it will default to `eq`.
|
118
|
+
|
119
|
+
if message is omitted, it will default to `'Condition met!'`
|
120
|
+
|
121
|
+
### Usage example
|
122
|
+
Single rake task is added to Rails project, if gem is included in Rails-based project. You can call it with
|
123
|
+
`bundle exec rake remind_me:check_reminders`. It will use `.` directory by default, if you need to customize this
|
124
|
+
add specific task in one of your `.rake` files and specify another directory, like so:
|
125
|
+
```ruby
|
126
|
+
require 'remind_me'
|
127
|
+
|
128
|
+
desc 'picks up REMIND_ME comments from codebase and checks if their conditions are met'
|
129
|
+
task custom_check_reminders: :environment do
|
130
|
+
RemindMe::Runner.new.check_reminders('/some/other/directory/')
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
If `Rails` is defined, `Rails.logger` will be used for printing results, otherwise `puts` will be used. Make sure your Rails logger
|
135
|
+
is configured to work properly from within rake task.
|
136
|
+
|
137
|
+
## Adding custom reminders
|
138
|
+
You can define custom reminders in your code by registering your reminder class with `RemindMe::Reminder::Generator`. For example:
|
139
|
+
```ruby
|
140
|
+
# in initializers/timed_reminder.rb
|
141
|
+
module Timed
|
142
|
+
class Reminder < RemindMe::Reminder::BaseReminder
|
143
|
+
apply_to_hash_with %i[after_time]
|
144
|
+
validate_hash_ast key: :message, value_types: %i[str], default_value: 'Condition met!'
|
145
|
+
|
146
|
+
def conditions_met?
|
147
|
+
Time.parse(hash_after_time).past?
|
148
|
+
end
|
149
|
+
|
150
|
+
def validation_errors
|
151
|
+
Time.parse(hash_after_time)
|
152
|
+
rescue StandardError => e
|
153
|
+
["'after_time' holds value that can't be parsed into time (#{e.message})"]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
158
|
+
Subclassing `RemindMe::Reminder::BaseReminder` will automatically register it as one of potential reminder processors.
|
159
|
+
|
160
|
+
## Future work
|
161
|
+
|
162
|
+
Expanding list of available reminders with other useful ones, for example:
|
163
|
+
- time based (we want to check something after specified date?)
|
164
|
+
- git_tag based ones (maybe we want to revisit something after version X is released?)
|
165
|
+
- file gets modified/deleted (when hash changes or file is missing)
|
166
|
+
- OS version changes...
|
167
|
+
|
168
|
+
Making use of Async ruby, al least for versions that have minimum ruby requirement > 3.0
|
169
|
+
(after we fetch all ruby files, creating and looking for reminders should be easily parallelizable)
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
## Development
|
174
|
+
|
175
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
176
|
+
|
177
|
+
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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
178
|
+
|
179
|
+
## Contributing
|
180
|
+
|
181
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/nikola-maric/remind_me. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/remind_me/blob/master/CODE_OF_CONDUCT.md).
|
182
|
+
|
183
|
+
## License
|
184
|
+
|
185
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
186
|
+
|
187
|
+
## Code of Conduct
|
188
|
+
|
189
|
+
Everyone interacting in the RemindMe project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/remind_me/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'remind_me'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'remind_me'
|
4
|
+
require 'rails'
|
5
|
+
|
6
|
+
module RemindMe
|
7
|
+
class Railtie < Rails::Railtie
|
8
|
+
|
9
|
+
railtie_name :remind_me
|
10
|
+
|
11
|
+
rake_tasks do
|
12
|
+
path = File.expand_path(__dir__)
|
13
|
+
Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../utils/hash_ast_manipulations'
|
4
|
+
require_relative 'generator'
|
5
|
+
|
6
|
+
module RemindMe
|
7
|
+
module Reminder
|
8
|
+
class BaseReminder
|
9
|
+
extend RemindMe::Utils::HashASTManipulations
|
10
|
+
|
11
|
+
attr_reader :reminder_comment_ast, :source_location
|
12
|
+
|
13
|
+
def self.inherited(base)
|
14
|
+
RemindMe::Reminder::Generator.register(base)
|
15
|
+
super(base)
|
16
|
+
end
|
17
|
+
|
18
|
+
def conditions_met?
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def validation_errors
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"#<#{self.class}, source: #{source_location}>"
|
28
|
+
end
|
29
|
+
|
30
|
+
def message
|
31
|
+
"#{hash_message} at #{source_location}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def initialize(reminder_comment_ast, source_location)
|
37
|
+
@reminder_comment_ast = reminder_comment_ast
|
38
|
+
@source_location = source_location
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_reminder'
|
4
|
+
require_relative '../utils/versions'
|
5
|
+
require_relative '../utils/hash_ast_manipulations'
|
6
|
+
|
7
|
+
module RemindMe
|
8
|
+
module Reminder
|
9
|
+
class GemVersionReminder < BaseReminder
|
10
|
+
include RemindMe::Utils::Versions
|
11
|
+
|
12
|
+
apply_to_hash_with %i[gem]
|
13
|
+
|
14
|
+
validate_hash_ast key: :gem, value_types: %i[str sym]
|
15
|
+
validate_hash_ast key: :version, value_types: %i[str]
|
16
|
+
validate_hash_ast key: :message, value_types: %i[str], default_value: 'Condition met!'
|
17
|
+
validate_hash_ast key: :condition, value_types: %i[sym str], default_value: :eq
|
18
|
+
|
19
|
+
def conditions_met?
|
20
|
+
target_version = hash_version
|
21
|
+
# if no version is specified, look for any version
|
22
|
+
if target_version.nil? || target_version.empty?
|
23
|
+
gem_installed?(hash_gem)
|
24
|
+
else
|
25
|
+
return false unless INSTALLED_GEMS[hash_gem]
|
26
|
+
|
27
|
+
condition = hash_condition
|
28
|
+
target_gem_version = Gem::Version.new(target_version)
|
29
|
+
installed_gem_version = INSTALLED_GEMS[hash_gem]
|
30
|
+
compare_version_numbers(target_gem_version, installed_gem_version, condition.to_sym)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def validation_errors
|
35
|
+
errors = super
|
36
|
+
errors << gem_missing_message unless gem_installed?(hash_gem)
|
37
|
+
errors << invalid_condition_message(source_location, hash_condition) unless valid_condition?(hash_condition)
|
38
|
+
errors << malformed_version_string_message unless valid_version_string?(hash_version)
|
39
|
+
errors
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def gem_missing_message
|
45
|
+
"REMIND_ME comment in #{source_location} mentions '#{hash_gem}' gem, but that gem is not installed"
|
46
|
+
end
|
47
|
+
|
48
|
+
def malformed_version_string_message
|
49
|
+
"REMIND_ME comment in #{source_location} mentions '#{hash_gem}' gem, but version specified: '#{hash_version}'"\
|
50
|
+
' is not proper version string'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'invalid_reminder'
|
4
|
+
|
5
|
+
module RemindMe
|
6
|
+
module Reminder
|
7
|
+
class Generator
|
8
|
+
|
9
|
+
def self.register(reminder_klass)
|
10
|
+
@registered_reminders ||= Set.new
|
11
|
+
@registered_reminders << reminder_klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.registered_reminders
|
15
|
+
@registered_reminders.dup
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.load_predefined_reminders
|
19
|
+
predefined_remiders = Dir.entries(File.dirname(__FILE__)).select do |path|
|
20
|
+
!path.end_with?('base_reminder.rb') && path.end_with?('_reminder.rb')
|
21
|
+
end
|
22
|
+
predefined_remiders.each { |f| require_relative f }
|
23
|
+
end
|
24
|
+
|
25
|
+
load_predefined_reminders
|
26
|
+
|
27
|
+
def self.generate(source_location, reminder_comment, parser)
|
28
|
+
parser.reset
|
29
|
+
begin
|
30
|
+
reminder_comment_ast = parser.class.parse(reminder_comment)
|
31
|
+
rescue Parser::SyntaxError => e
|
32
|
+
return [unparsable_reminder(source_location, e)]
|
33
|
+
end
|
34
|
+
relevant_reminders_classes = relevant_reminders_classes(reminder_comment_ast)
|
35
|
+
return [unknown_reminder(source_location, reminder_comment)] if relevant_reminders_classes.empty?
|
36
|
+
|
37
|
+
create_reminders_from(reminder_comment_ast, source_location, relevant_reminders_classes)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.relevant_reminders_classes(reminder_comment_ast)
|
41
|
+
registered_reminders.select do |reminder_class|
|
42
|
+
reminder_class.applicable_to_ast?(reminder_comment_ast)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.create_reminders_from(reminder_comment_ast, source_location, relevant_reminders_classes)
|
47
|
+
relevant_reminders_classes.map do |reminder_class|
|
48
|
+
reminder_class.build_from(reminder_comment_ast, source_location)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.unknown_reminder(source_location, reminder_comment)
|
53
|
+
RemindMe::Reminder::InvalidReminder
|
54
|
+
.new(source_location,
|
55
|
+
"REMIND_ME comment in #{source_location}: found '#{reminder_comment}' but it was "\
|
56
|
+
'not applicable to any known reminder processors')
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.unparsable_reminder(source_location, error)
|
60
|
+
RemindMe::Reminder::InvalidReminder
|
61
|
+
.new(source_location,
|
62
|
+
"REMIND_ME comment in #{source_location}: unable to parse, message: #{error.message}")
|
63
|
+
end
|
64
|
+
|
65
|
+
private_class_method :unknown_reminder,
|
66
|
+
:unparsable_reminder,
|
67
|
+
:relevant_reminders_classes,
|
68
|
+
:create_reminders_from
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemindMe
|
4
|
+
module Reminder
|
5
|
+
class InvalidReminder
|
6
|
+
attr_reader :source_location, :message
|
7
|
+
|
8
|
+
def initialize(source_location, message)
|
9
|
+
@source_location = source_location
|
10
|
+
@message = message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_reminder'
|
4
|
+
require_relative '../utils/versions'
|
5
|
+
|
6
|
+
module RemindMe
|
7
|
+
module Reminder
|
8
|
+
class MissingGemReminder < BaseReminder
|
9
|
+
include RemindMe::Utils::Versions
|
10
|
+
|
11
|
+
apply_to_hash_with %i[missing_gem]
|
12
|
+
validate_hash_ast key: :message, value_types: %i[str], default_value: 'Condition met!'
|
13
|
+
|
14
|
+
def conditions_met?
|
15
|
+
!gem_installed?(hash_missing_gem)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_reminder'
|
4
|
+
require_relative '../utils/versions'
|
5
|
+
|
6
|
+
module RemindMe
|
7
|
+
module Reminder
|
8
|
+
class RubyVersionReminder < BaseReminder
|
9
|
+
include RemindMe::Utils::Versions
|
10
|
+
|
11
|
+
apply_to_hash_with %i[ruby_version]
|
12
|
+
|
13
|
+
validate_hash_ast key: :message, value_types: %i[str], default_value: 'Condition met!'
|
14
|
+
validate_hash_ast key: :condition, value_types: %i[sym str], default_value: :eq
|
15
|
+
|
16
|
+
def conditions_met?
|
17
|
+
condition = hash_condition
|
18
|
+
target_ruby_version = Gem::Version.new(hash_ruby_version)
|
19
|
+
installed_ruby_version = Gem::Version.new(RUBY_VERSION)
|
20
|
+
compare_version_numbers(target_ruby_version, installed_ruby_version, condition)
|
21
|
+
end
|
22
|
+
|
23
|
+
def validation_errors
|
24
|
+
errors = super
|
25
|
+
errors << invalid_ruby_version_message if hash_ruby_version.nil? || hash_ruby_version == ''
|
26
|
+
errors << malformed_version_string_message unless valid_version_string?(hash_ruby_version)
|
27
|
+
errors << invalid_condition_message(source_location, hash_condition) unless valid_condition?(hash_condition)
|
28
|
+
errors
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def invalid_ruby_version_message
|
34
|
+
"REMIND_ME comment on #{source_location} has blank ruby version, you must specify version string"
|
35
|
+
end
|
36
|
+
|
37
|
+
def malformed_version_string_message
|
38
|
+
"REMIND_ME comment in #{source_location} mentions '#{hash_ruby_version}' ruby version, but "\
|
39
|
+
' that is not a proper version string'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'parser/current'
|
4
|
+
require 'find'
|
5
|
+
|
6
|
+
require_relative 'version'
|
7
|
+
require_relative 'bail_out'
|
8
|
+
require_relative 'reminder/generator'
|
9
|
+
require_relative 'utils/logger'
|
10
|
+
require_relative 'utils/result_printer'
|
11
|
+
|
12
|
+
module RemindMe
|
13
|
+
class Runner
|
14
|
+
include BailOut
|
15
|
+
include Utils::Logger
|
16
|
+
|
17
|
+
attr_reader :parser
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@parser = Parser::CurrentRuby.new
|
21
|
+
parser.diagnostics.consumer = ->(_message) { }
|
22
|
+
parser.diagnostics.ignore_warnings = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def check_reminders(check_path: '.')
|
26
|
+
log_info "Checking #{check_path} for any REMIND_ME comments..."
|
27
|
+
all_reminders = collect_reminders(check_path)
|
28
|
+
Utils::ResultPrinter.new(all_reminders).print_results
|
29
|
+
end
|
30
|
+
|
31
|
+
def collect_reminders(parse_path)
|
32
|
+
files = collect_ruby_files(parse_path)
|
33
|
+
bail_out!('Need something to parse!') if files.empty?
|
34
|
+
log_info "Found #{files.size} ruby files"
|
35
|
+
raw_comments = collect_relevant_comments(files)
|
36
|
+
raw_comments.flat_map { |raw_comment| RemindMe::Reminder::Generator.generate(raw_comment[0], raw_comment[1], parser) }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def collect_relevant_comments(files)
|
42
|
+
files.flat_map { |file| all_file_comments(file) }
|
43
|
+
.map { |x| [x.location.expression.to_s, x.text.split('REMIND_ME:', 2)] }
|
44
|
+
.select { |x| x[1].size == 2 }
|
45
|
+
.map { |x| [x[0], x[1][1].split("\n").first] }
|
46
|
+
end
|
47
|
+
|
48
|
+
def all_file_comments(file)
|
49
|
+
parser.reset
|
50
|
+
source = File.read(file).force_encoding(parser.default_encoding)
|
51
|
+
buffer = Parser::Source::Buffer.new(file)
|
52
|
+
buffer.source = source
|
53
|
+
parser.parse_with_comments(buffer).last
|
54
|
+
end
|
55
|
+
|
56
|
+
def collect_ruby_files(parse_path)
|
57
|
+
files = []
|
58
|
+
if File.directory?(parse_path)
|
59
|
+
Find.find(parse_path) do |path|
|
60
|
+
files << path if path.end_with? '.rb'
|
61
|
+
end
|
62
|
+
elsif parse_path.end_with? '.rb'
|
63
|
+
files << parse_path
|
64
|
+
end
|
65
|
+
files
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../reminder/invalid_reminder'
|
4
|
+
|
5
|
+
module RemindMe
|
6
|
+
module Utils
|
7
|
+
module HashASTManipulations
|
8
|
+
|
9
|
+
# Will look for string/symbol keys with specified names in the comment, and if all are found, will return true
|
10
|
+
def apply_to_hash_with(key_values)
|
11
|
+
# Method for determining if we should consider given reminder for this AST
|
12
|
+
define_singleton_method('applicable_to_ast?') do |ast|
|
13
|
+
ast.type == :hash && key_values.all? { |key_value| key_present?(ast, key_value) }
|
14
|
+
end
|
15
|
+
# Building a Reminder from AST, creating invalid one if any of validations returns any non-nil value
|
16
|
+
define_singleton_method('build_from') do |reminder_comment_ast, source_location|
|
17
|
+
create_reminder_from_ast(reminder_comment_ast, source_location)
|
18
|
+
end
|
19
|
+
key_values.each do |key|
|
20
|
+
validate_hash_ast(key: key, value_types: %i[sym str])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_hash_ast(key:, value_types:, **options)
|
25
|
+
@hash_ast_default_values ||= {}
|
26
|
+
@hash_ast_default_values[key] = options[:default_value] if options.key?(:default_value)
|
27
|
+
create_hash_value_accessor_method(key)
|
28
|
+
define_singleton_method("validate_hash_ast_#{key}") do |ast, source_location|
|
29
|
+
return "REMIND_ME comment in #{source_location} is not a Hash" unless ast.type == :hash
|
30
|
+
|
31
|
+
pair = ast_hash_pair(ast, key)
|
32
|
+
# Valid pair was not found...
|
33
|
+
if pair.nil?
|
34
|
+
# ... and we don't have default value set for it
|
35
|
+
unless @hash_ast_default_values.key?(key)
|
36
|
+
"REMIND_ME comment in #{source_location}: value for '#{key}' could not be found, key needs to be "\
|
37
|
+
"either String or Symbol. If not set 'default_value' can be used, but that one was not given as well"
|
38
|
+
end
|
39
|
+
# Pair was found...
|
40
|
+
else
|
41
|
+
# ... but it does not have proper value type
|
42
|
+
unless valid_hash_ast_value?(pair, value_types)
|
43
|
+
"REMIND_ME comment in #{source_location}: value under specified key '#{key}' does not have allowed "\
|
44
|
+
"type (it has '#{hash_ast_pair_value_type(pair)}'), allowed types are #{value_types}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_reminder_from_ast(reminder_comment_ast, source_location)
|
51
|
+
# We first perform AST validation
|
52
|
+
ast_validation_errors = singleton_methods.select { |method| method.to_s.start_with?('validate_hash_ast_') }
|
53
|
+
.map { |method| send(method, reminder_comment_ast, source_location) }
|
54
|
+
.compact
|
55
|
+
if ast_validation_errors.empty?
|
56
|
+
# AST looks good, now we perform reminder-specific validations
|
57
|
+
reminder = new(reminder_comment_ast, source_location)
|
58
|
+
validation_errors = reminder.validation_errors
|
59
|
+
if validation_errors.empty?
|
60
|
+
reminder
|
61
|
+
else
|
62
|
+
RemindMe::Reminder::InvalidReminder.new(source_location, validation_errors.join(';'))
|
63
|
+
end
|
64
|
+
else
|
65
|
+
RemindMe::Reminder::InvalidReminder.new(source_location, ast_validation_errors.join(';'))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_hash_value_accessor_method(key)
|
70
|
+
return if method_defined?("hash_#{key}")
|
71
|
+
|
72
|
+
send(:define_method, "hash_#{key}") do
|
73
|
+
comment_ast = instance_variable_get('@reminder_comment_ast')
|
74
|
+
value = self.class.hash_ast_pair_value(self.class.ast_hash_pair(comment_ast, key))
|
75
|
+
default_values = self.class.instance_variable_get('@hash_ast_default_values')
|
76
|
+
if (value.nil? || value == '') && default_values.key?(key)
|
77
|
+
default_values[key]
|
78
|
+
else
|
79
|
+
value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def singleton_method_defined?(method_name)
|
85
|
+
singleton_methods.any? { |method| method.to_s == method_name }
|
86
|
+
end
|
87
|
+
|
88
|
+
def ast_hash_pair(hash_ast, key_value)
|
89
|
+
children_of_type(hash_ast, :pair).find { |pair| valid_hash_ast_key?(pair, key_value) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def key_present?(reminder_comment_ast, key_value)
|
93
|
+
children_of_type(reminder_comment_ast, :pair).any? { |pair| valid_hash_ast_key?(pair, key_value) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def children_of_type(reminder_comment_ast, type)
|
97
|
+
reminder_comment_ast.children.select { |child| child.type.to_s == type.to_s }
|
98
|
+
end
|
99
|
+
|
100
|
+
# key is either a symbol or string
|
101
|
+
def valid_hash_ast_key?(ast_pair, key_value)
|
102
|
+
key = ast_pair.children.first
|
103
|
+
%i[sym str].include?(key.type) && key_value.to_s == key.to_a.first.to_s
|
104
|
+
end
|
105
|
+
|
106
|
+
def valid_hash_ast_value?(ast_pair, allowed_types)
|
107
|
+
value = ast_pair.children[1]
|
108
|
+
allowed_types.include?(value.type)
|
109
|
+
end
|
110
|
+
|
111
|
+
def hash_ast_pair_value_type(ast_pair)
|
112
|
+
ast_pair.children[1].type
|
113
|
+
end
|
114
|
+
|
115
|
+
def hash_ast_pair_value(ast_pair)
|
116
|
+
return nil if ast_pair.nil? || ast_pair.children.nil? || ast_pair.children.size != 2
|
117
|
+
|
118
|
+
ast_pair.children[1].to_a.first
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemindMe
|
4
|
+
module Utils
|
5
|
+
module Logger
|
6
|
+
|
7
|
+
def log_info(msg)
|
8
|
+
rails_being_used? ? log_with_rails(green(msg), :info) : puts(green(msg))
|
9
|
+
end
|
10
|
+
|
11
|
+
def log_error(msg)
|
12
|
+
rails_being_used? ? log_with_rails(red(msg), :error) : puts(red(msg))
|
13
|
+
end
|
14
|
+
|
15
|
+
def colorize(color_code, string)
|
16
|
+
"\e[#{color_code}m#{string}\e[0m"
|
17
|
+
end
|
18
|
+
|
19
|
+
def red(string)
|
20
|
+
colorize(31, string)
|
21
|
+
end
|
22
|
+
|
23
|
+
def green(string)
|
24
|
+
colorize(32, string)
|
25
|
+
end
|
26
|
+
|
27
|
+
def rails_being_used?
|
28
|
+
defined?(Rails)
|
29
|
+
end
|
30
|
+
|
31
|
+
def log_with_rails(msg, severity)
|
32
|
+
Rails.logger.send(severity, msg)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemindMe
|
4
|
+
module Utils
|
5
|
+
class ResultPrinter
|
6
|
+
include Utils::Logger
|
7
|
+
|
8
|
+
attr_reader :reminders
|
9
|
+
|
10
|
+
def initialize(reminders)
|
11
|
+
@reminders = reminders
|
12
|
+
end
|
13
|
+
|
14
|
+
def print_results
|
15
|
+
if reminders.empty?
|
16
|
+
log_info 'No reminders found'
|
17
|
+
else
|
18
|
+
valid_reminders = reminders.reject { |r| r.is_a?(RemindMe::Reminder::InvalidReminder) }
|
19
|
+
invalid_reminders = reminders.select { |r| r.is_a?(RemindMe::Reminder::InvalidReminder) }
|
20
|
+
if invalid_reminders.size.positive?
|
21
|
+
message = invalid_reminders.map(&:message).join("\n")
|
22
|
+
log_error(message)
|
23
|
+
abort
|
24
|
+
else
|
25
|
+
valid_condition_met_reminders = valid_reminders.select(&:conditions_met?)
|
26
|
+
if valid_condition_met_reminders.size.positive?
|
27
|
+
message = valid_condition_met_reminders.map(&:message).join("\n")
|
28
|
+
log_error(message)
|
29
|
+
abort
|
30
|
+
else
|
31
|
+
log_info "Found #{reminders.size} REMIND_ME comment(s), but none of the conditions were met..yet!"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemindMe
|
4
|
+
module Utils
|
5
|
+
module Versions
|
6
|
+
INSTALLED_GEMS = Gem::Specification.map { |a| [a.name, a.version] }.to_h
|
7
|
+
|
8
|
+
def compare_version_numbers(target_version, current_version, comparator)
|
9
|
+
current_version.__send__(condition_comparators[comparator.to_sym], target_version)
|
10
|
+
end
|
11
|
+
|
12
|
+
def gem_installed?(gem)
|
13
|
+
INSTALLED_GEMS.key?(gem.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid_condition?(condition)
|
17
|
+
condition_comparators.keys.flat_map { |x| [x, x.to_s] }.include?(condition)
|
18
|
+
end
|
19
|
+
|
20
|
+
def condition_comparators
|
21
|
+
%i[lt lte gt gte eq].zip(%i[< <= > >= ==]).to_h
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid_version_string?(version_string)
|
25
|
+
Gem::Version.new(version_string)
|
26
|
+
true
|
27
|
+
rescue ArgumentError => _e
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def invalid_condition_message(source_location, condition)
|
32
|
+
"REMIND_ME comment on #{source_location} has invalid condition: #{condition}, only "\
|
33
|
+
'lt, lte, gt, gte, eq are possible, or you can omit it entirely (it will default to eq)'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/remind_me.rb
ADDED
data/remind_me.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
require_relative 'lib/remind_me/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'remind_me'
|
7
|
+
spec.version = RemindMe::VERSION
|
8
|
+
spec.authors = ['Nikola Marić']
|
9
|
+
spec.email = ['nkl.maric@gmail.net']
|
10
|
+
|
11
|
+
spec.summary = 'Processor of REMIND_ME comments in the code, reminding us to revisit parts of code'
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = 'https://github.com/nikola-maric/remind_me'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
19
|
+
spec.metadata['changelog_uri'] = spec.homepage
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = 'exe'
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ['lib']
|
29
|
+
|
30
|
+
spec.add_development_dependency 'rake'
|
31
|
+
spec.add_development_dependency 'rspec'
|
32
|
+
spec.add_development_dependency 'rubocop'
|
33
|
+
spec.add_development_dependency 'simplecov'
|
34
|
+
|
35
|
+
spec.add_dependency 'parser', '~> 3.0.2.0'
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: remind_me
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nikola Marić
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-12-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
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
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: parser
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.0.2.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.0.2.0
|
83
|
+
description: Processor of REMIND_ME comments in the code, reminding us to revisit
|
84
|
+
parts of code
|
85
|
+
email:
|
86
|
+
- nkl.maric@gmail.net
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".github/workflows/main.yml"
|
92
|
+
- ".gitignore"
|
93
|
+
- ".rspec"
|
94
|
+
- ".rubocop.yml"
|
95
|
+
- CODE_OF_CONDUCT.md
|
96
|
+
- Gemfile
|
97
|
+
- Gemfile.lock
|
98
|
+
- LICENSE.txt
|
99
|
+
- README.md
|
100
|
+
- Rakefile
|
101
|
+
- bin/console
|
102
|
+
- bin/setup
|
103
|
+
- lib/remind_me.rb
|
104
|
+
- lib/remind_me/bail_out.rb
|
105
|
+
- lib/remind_me/railtie.rb
|
106
|
+
- lib/remind_me/reminder/base_reminder.rb
|
107
|
+
- lib/remind_me/reminder/gem_version_reminder.rb
|
108
|
+
- lib/remind_me/reminder/generator.rb
|
109
|
+
- lib/remind_me/reminder/invalid_reminder.rb
|
110
|
+
- lib/remind_me/reminder/missing_gem_reminder.rb
|
111
|
+
- lib/remind_me/reminder/ruby_version_reminder.rb
|
112
|
+
- lib/remind_me/runner.rb
|
113
|
+
- lib/remind_me/tasks/remind_me.rake
|
114
|
+
- lib/remind_me/utils/hash_ast_manipulations.rb
|
115
|
+
- lib/remind_me/utils/logger.rb
|
116
|
+
- lib/remind_me/utils/result_printer.rb
|
117
|
+
- lib/remind_me/utils/versions.rb
|
118
|
+
- lib/remind_me/version.rb
|
119
|
+
- remind_me.gemspec
|
120
|
+
homepage: https://github.com/nikola-maric/remind_me
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata:
|
124
|
+
homepage_uri: https://github.com/nikola-maric/remind_me
|
125
|
+
source_code_uri: https://github.com/nikola-maric/remind_me
|
126
|
+
changelog_uri: https://github.com/nikola-maric/remind_me
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: 2.3.0
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubygems_version: 3.1.4
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Processor of REMIND_ME comments in the code, reminding us to revisit parts
|
146
|
+
of code
|
147
|
+
test_files: []
|