remind_me 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ 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
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /.idea/
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
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/**/*'
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RemindMe
4
+ module BailOut
5
+ class Error < StandardError; end
6
+
7
+ def bail_out!(message)
8
+ raise RemindMe::BailOut::Error, message
9
+ end
10
+ end
11
+ end
@@ -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,8 @@
1
+ require 'remind_me'
2
+
3
+ namespace :remind_me do
4
+ desc 'picks up REMIND_ME comments from codebase and checks if their conditions are met'
5
+ task check_reminders: :environment do
6
+ RemindMe::Runner.new.check_reminders
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RemindMe
4
+ VERSION = '0.2.0'
5
+ end
data/lib/remind_me.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'remind_me/runner'
3
+ require_relative 'remind_me/railtie' if defined?(Rails::Railtie)
4
+
5
+
6
+
7
+
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: []