remind_me 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +11 -24
- data/.gitignore +3 -1
- data/CLAUDE.md +57 -0
- data/Cargo.toml +3 -0
- data/Gemfile +3 -0
- data/README.md +1 -1
- data/Rakefile +9 -0
- data/ext/remind_me/Cargo.toml +22 -0
- data/ext/remind_me/extconf.rb +11 -0
- data/ext/remind_me/src/bench.rs +35 -0
- data/ext/remind_me/src/lib.rs +21 -0
- data/ext/remind_me/src/scanner.rs +206 -0
- data/lib/remind_me/runner.rb +34 -1
- data/lib/remind_me/version.rb +1 -1
- data/remind_me.gemspec +4 -1
- metadata +44 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7fa0e33f33da3bbe35b1245625c5c605c5e20d14e90f5c710676224245a456fa
|
|
4
|
+
data.tar.gz: 0bea7706ce46a895d36203bff906e245c5d42adf23c8b68d6e6f7a8de4255ec8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d59df0fa184b0166e0b246eca2e58b3e2ec3bdbf86a16d07f54298001a4b99c98410e3c6a41de5b57c46837ea875a651a5e520ac91bce78e8832977892babaf
|
|
7
|
+
data.tar.gz: 10e9483a5de777738edc140fd88db75d49a5133c466798e2212e670fcabd358995744e8c2e6e882559caa85817f1ea30b4e6c45823076e8544689e9714f3d862
|
data/.github/workflows/main.yml
CHANGED
|
@@ -1,40 +1,27 @@
|
|
|
1
1
|
name: Tests
|
|
2
2
|
|
|
3
|
-
on: [push
|
|
3
|
+
on: [push]
|
|
4
4
|
|
|
5
5
|
jobs:
|
|
6
6
|
build:
|
|
7
|
-
runs-on:
|
|
7
|
+
runs-on: ${{ matrix.os }}
|
|
8
8
|
strategy:
|
|
9
9
|
fail-fast: false
|
|
10
10
|
matrix:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
requires-older-rubygems: true
|
|
15
|
-
requires-older-bundler: true
|
|
16
|
-
- ruby: "2.4.10"
|
|
17
|
-
requires-older-rubygems: true
|
|
18
|
-
requires-older-bundler: true
|
|
19
|
-
- ruby: "jruby-9.2"
|
|
20
|
-
requires-older-bundler: true
|
|
21
|
-
- ruby: "jruby-9.3"
|
|
22
|
-
requires-older-bundler: true
|
|
11
|
+
os: [ubuntu-latest, ubuntu-24.04-arm, macos-14]
|
|
12
|
+
# TODO: Add Ruby 4.0 and 4.1 once Magnus gem releases official support
|
|
13
|
+
ruby: ["2.7.8", "3.0.7", "3.1.7", "3.2.10", "3.3.10", "3.4.8"]
|
|
23
14
|
steps:
|
|
24
|
-
- uses: actions/checkout@
|
|
15
|
+
- uses: actions/checkout@v4
|
|
25
16
|
- name: Set up Ruby
|
|
26
17
|
uses: ruby/setup-ruby@v1
|
|
27
18
|
with:
|
|
28
19
|
ruby-version: ${{ matrix.ruby }}
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
- if: ${{ matrix.requires-older-bundler }}
|
|
32
|
-
run: gem install bundler -v 2.3.26
|
|
20
|
+
- name: Install Rust toolchain
|
|
21
|
+
uses: dtolnay/rust-toolchain@stable
|
|
33
22
|
- name: Bundle install
|
|
34
23
|
run: bundle install --jobs=3
|
|
35
|
-
- name:
|
|
24
|
+
- name: Compile native extension
|
|
25
|
+
run: bundle exec rake compile
|
|
26
|
+
- name: Run tests
|
|
36
27
|
run: bundle exec rake spec
|
|
37
|
-
if: "!startsWith(matrix.ruby, 'jruby')"
|
|
38
|
-
- name: Run tests (JRuby)
|
|
39
|
-
run: JRUBY_OPTS=--debug bundle exec rake spec
|
|
40
|
-
if: startsWith(matrix.ruby, 'jruby')
|
data/.gitignore
CHANGED
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
`remind_me` is a Ruby gem that provides a non-invasive code reminder system. It scans Ruby files for special `REMIND_ME:` comments (written as Ruby hash syntax) and checks whether specified conditions are met (gem versions, Ruby versions, missing gems). Designed to run in CI pipelines so developers revisit code when conditions are satisfied. Comments don't affect runtime behavior.
|
|
8
|
+
|
|
9
|
+
## Common Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bundle exec rake spec # Run tests
|
|
13
|
+
bundle exec rake rubocop # Run linter only
|
|
14
|
+
bundle exec rspec spec/remind_me/runner_spec.rb # Single test file
|
|
15
|
+
bundle exec rspec spec/remind_me/runner_spec.rb:42 # Single test by line
|
|
16
|
+
bundle exec rake compile # Compile Rust native extension
|
|
17
|
+
bundle exec rake install # Install gem locally
|
|
18
|
+
cargo test --manifest-path ext/remind_me/Cargo.toml # Run Rust unit tests
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Note: On Ruby 2.7 with bundler version conflicts, use `bundle _2.4.22_` prefix.
|
|
22
|
+
|
|
23
|
+
## Architecture
|
|
24
|
+
|
|
25
|
+
**Entry point:** `lib/remind_me.rb` → loads `Runner` and optional `Railtie`
|
|
26
|
+
|
|
27
|
+
**Core flow:** `Runner.check_reminders()` → scans for `REMIND_ME:` comments → `Generator.generate()` creates reminder objects → `ResultPrinter.print_results()` reports or exits with error.
|
|
28
|
+
|
|
29
|
+
**Native Rust extension (optional):** When compiled (`rake compile`), a Rust extension (`ext/remind_me/`) using `ignore` (parallel dir walking) and `memchr` (fast string search) replaces the Ruby file scanning. Exposed as `RemindMe::Native.scan_for_remind_me_comments(path)` via Magnus. Falls back to pure Ruby (`parser` gem) if the extension isn't available (JRuby, no Rust toolchain). The data contract between scanning and processing is `[source_location_string, comment_text_string]` tuples.
|
|
30
|
+
|
|
31
|
+
**Plugin system with self-registration:** `BaseReminder` subclasses auto-register via `inherited` hook into `Generator`'s registry. New reminder types are added by subclassing `BaseReminder` and using the hash AST DSL (`apply_to_hash_with`, `validate_hash_ast`).
|
|
32
|
+
|
|
33
|
+
**Built-in reminder types:**
|
|
34
|
+
- `GemVersionReminder` — `{ gem: 'rails', version: '6', condition: :gte, message: '...' }`
|
|
35
|
+
- `RubyVersionReminder` — `{ ruby_version: '3', condition: :gte, message: '...' }`
|
|
36
|
+
- `MissingGemReminder` — `{ missing_gem: 'thor', message: '...' }`
|
|
37
|
+
|
|
38
|
+
**Key modules:**
|
|
39
|
+
- `HashAstManipulations` — DSL for declaring/validating hash keys in parsed AST nodes; generates accessor and validation methods dynamically
|
|
40
|
+
- `BailOut` — error handling mixin used across reminder classes
|
|
41
|
+
- `Utils::Versions` — version comparison logic supporting operators: `lt`, `lte`, `gt`, `gte`, `eq`
|
|
42
|
+
|
|
43
|
+
**Parallel processing:** Uses `parallel` gem for concurrent reminder processing. Handles older parallel gem API differences. File scanning parallelism is handled by the Rust extension (or `parallel` in fallback mode).
|
|
44
|
+
|
|
45
|
+
**Rails integration:** `Railtie` auto-loads `remind_me:check_reminders` rake task when Rails is present.
|
|
46
|
+
|
|
47
|
+
## Test Structure
|
|
48
|
+
|
|
49
|
+
Tests use RSpec with SimpleCov (90% overall minimum, 80% per-file). Fixture files live in `spec/testing_grounds/` organized by `single_line/` and `multi_line/` comment styles. The `runner_spec.rb` is the main integration test; unit tests are organized by reminder type under `spec/remind_me/reminder/`.
|
|
50
|
+
|
|
51
|
+
## Style
|
|
52
|
+
|
|
53
|
+
RuboCop config: single quotes (double for interpolation), 120-char line limit, 14 method length limit. Documentation cop disabled for specs.
|
|
54
|
+
|
|
55
|
+
## CI
|
|
56
|
+
|
|
57
|
+
GitHub Actions has two jobs: `build` (pure Ruby, all Ruby versions 2.3.7–3.2.2 + JRuby 9.2–9.4) and `build-native` (with Rust extension, Ruby 2.7.4–3.2.2).
|
data/Cargo.toml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -126,7 +126,7 @@ require 'remind_me'
|
|
|
126
126
|
|
|
127
127
|
desc 'picks up REMIND_ME comments from codebase and checks if their conditions are met'
|
|
128
128
|
task custom_check_reminders: :environment do
|
|
129
|
-
RemindMe::Runner.
|
|
129
|
+
RemindMe::Runner.check_reminders(check_path: '/some/other/directory/')
|
|
130
130
|
end
|
|
131
131
|
```
|
|
132
132
|
|
data/Rakefile
CHANGED
|
@@ -9,4 +9,13 @@ require 'rubocop/rake_task'
|
|
|
9
9
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
|
+
begin
|
|
13
|
+
require 'rb_sys/extensiontask'
|
|
14
|
+
RbSys::ExtensionTask.new('remind_me_native') do |ext|
|
|
15
|
+
ext.lib_dir = 'lib/remind_me'
|
|
16
|
+
end
|
|
17
|
+
rescue LoadError
|
|
18
|
+
# rb_sys not available, skip native extension compilation task
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
task default: %i[spec rubocop]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "remind_me_native"
|
|
3
|
+
version = "0.7.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
publish = false
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
crate-type = ["cdylib", "rlib"]
|
|
9
|
+
|
|
10
|
+
[[bin]]
|
|
11
|
+
name = "bench"
|
|
12
|
+
path = "src/bench.rs"
|
|
13
|
+
required-features = []
|
|
14
|
+
|
|
15
|
+
[features]
|
|
16
|
+
default = ["ruby"]
|
|
17
|
+
ruby = ["magnus"]
|
|
18
|
+
|
|
19
|
+
[dependencies]
|
|
20
|
+
magnus = { version = "0.7", features = ["rb-sys"], optional = true }
|
|
21
|
+
ignore = "0.4"
|
|
22
|
+
memchr = "2"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mkmf'
|
|
4
|
+
|
|
5
|
+
if system('cargo --version > /dev/null 2>&1')
|
|
6
|
+
require 'rb_sys/mkmf'
|
|
7
|
+
create_rust_makefile('remind_me/remind_me_native')
|
|
8
|
+
else
|
|
9
|
+
File.write('Makefile', "all:\ninstall:\n")
|
|
10
|
+
$stderr.puts 'WARNING: Rust toolchain not found, skipping native extension build. Using pure-Ruby fallback.'
|
|
11
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
use std::env;
|
|
2
|
+
use std::time::Instant;
|
|
3
|
+
|
|
4
|
+
use remind_me_native::scanner;
|
|
5
|
+
|
|
6
|
+
fn main() {
|
|
7
|
+
let path = env::args()
|
|
8
|
+
.nth(1)
|
|
9
|
+
.unwrap_or_else(|| ".".to_string());
|
|
10
|
+
let iterations: usize = env::args()
|
|
11
|
+
.nth(2)
|
|
12
|
+
.and_then(|s| s.parse().ok())
|
|
13
|
+
.unwrap_or(100);
|
|
14
|
+
|
|
15
|
+
println!("Scanning: {}", path);
|
|
16
|
+
println!("Iterations: {}", iterations);
|
|
17
|
+
|
|
18
|
+
// Warmup
|
|
19
|
+
let results = scanner::scan_for_remind_me_comments(&path);
|
|
20
|
+
println!("Found {} REMIND_ME comments", results.len());
|
|
21
|
+
for (loc, text) in &results {
|
|
22
|
+
println!(" {} {}", loc, text.trim());
|
|
23
|
+
}
|
|
24
|
+
println!();
|
|
25
|
+
|
|
26
|
+
// Benchmark
|
|
27
|
+
let start = Instant::now();
|
|
28
|
+
for _ in 0..iterations {
|
|
29
|
+
let _ = scanner::scan_for_remind_me_comments(&path);
|
|
30
|
+
}
|
|
31
|
+
let elapsed = start.elapsed();
|
|
32
|
+
|
|
33
|
+
println!("Total: {:.3}s ({} iterations)", elapsed.as_secs_f64(), iterations);
|
|
34
|
+
println!("Per iteration: {:.4}s", elapsed.as_secs_f64() / iterations as f64);
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
pub mod scanner;
|
|
2
|
+
|
|
3
|
+
#[cfg(feature = "ruby")]
|
|
4
|
+
use magnus::{function, prelude::*, Error, Ruby};
|
|
5
|
+
|
|
6
|
+
#[cfg(feature = "ruby")]
|
|
7
|
+
fn scan_for_remind_me_comments(path: String) -> Result<Vec<(String, String)>, Error> {
|
|
8
|
+
Ok(scanner::scan_for_remind_me_comments(&path))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
#[cfg(feature = "ruby")]
|
|
12
|
+
#[magnus::init]
|
|
13
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
14
|
+
let module = ruby.define_module("RemindMe")?;
|
|
15
|
+
let native = module.define_module("Native")?;
|
|
16
|
+
native.define_module_function(
|
|
17
|
+
"scan_for_remind_me_comments",
|
|
18
|
+
function!(scan_for_remind_me_comments, 1),
|
|
19
|
+
)?;
|
|
20
|
+
Ok(())
|
|
21
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
use memchr::memmem;
|
|
2
|
+
use std::fs;
|
|
3
|
+
use std::path::Path;
|
|
4
|
+
use std::sync::Mutex;
|
|
5
|
+
|
|
6
|
+
const MARKER: &str = "REMIND_ME:";
|
|
7
|
+
const MARKER_LEN: usize = 10; // "REMIND_ME:".len()
|
|
8
|
+
|
|
9
|
+
/// A single REMIND_ME comment match with its source location and text.
|
|
10
|
+
pub struct Match {
|
|
11
|
+
pub source_location: String,
|
|
12
|
+
pub comment_text: String,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Scan a single file for REMIND_ME comments.
|
|
16
|
+
///
|
|
17
|
+
/// Handles both single-line (`# REMIND_ME: ...`) and block (`=begin`/`=end`) comments.
|
|
18
|
+
/// Returns matches as (source_location, comment_text) where:
|
|
19
|
+
/// - source_location is "path:line:1"
|
|
20
|
+
/// - comment_text is everything after "REMIND_ME:" on the matching line
|
|
21
|
+
pub fn scan_file(file_path: &str) -> Vec<Match> {
|
|
22
|
+
let content = match fs::read_to_string(file_path) {
|
|
23
|
+
Ok(c) => c,
|
|
24
|
+
Err(_) => return Vec::new(),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Quick check: skip file entirely if no REMIND_ME: present
|
|
28
|
+
if memmem::find(content.as_bytes(), MARKER.as_bytes()).is_none() {
|
|
29
|
+
return Vec::new();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let lines: Vec<&str> = content.lines().collect();
|
|
33
|
+
|
|
34
|
+
// First pass: identify =begin/=end block comment boundaries
|
|
35
|
+
let mut block_ranges: Vec<(usize, usize)> = Vec::new();
|
|
36
|
+
let mut current_begin: Option<usize> = None;
|
|
37
|
+
for (i, line) in lines.iter().enumerate() {
|
|
38
|
+
if line.starts_with("=begin") && current_begin.is_none() {
|
|
39
|
+
current_begin = Some(i + 1); // 1-indexed
|
|
40
|
+
} else if line.starts_with("=end") {
|
|
41
|
+
if let Some(begin_line) = current_begin {
|
|
42
|
+
block_ranges.push((begin_line, i + 1));
|
|
43
|
+
current_begin = None;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Second pass: find REMIND_ME: on each line
|
|
49
|
+
let finder = memmem::Finder::new(MARKER);
|
|
50
|
+
let mut results = Vec::new();
|
|
51
|
+
|
|
52
|
+
for (i, line) in lines.iter().enumerate() {
|
|
53
|
+
let line_num = i + 1; // 1-indexed
|
|
54
|
+
|
|
55
|
+
if let Some(pos) = finder.find(line.as_bytes()) {
|
|
56
|
+
let comment_text = &line[pos + MARKER_LEN..];
|
|
57
|
+
|
|
58
|
+
// Determine if this match is inside a comment
|
|
59
|
+
let in_block = block_ranges
|
|
60
|
+
.iter()
|
|
61
|
+
.any(|(begin, end)| line_num >= *begin && line_num <= *end);
|
|
62
|
+
|
|
63
|
+
if in_block {
|
|
64
|
+
// Block comment: report the =begin line number
|
|
65
|
+
let begin_line = block_ranges
|
|
66
|
+
.iter()
|
|
67
|
+
.find(|(begin, end)| line_num >= *begin && line_num <= *end)
|
|
68
|
+
.map(|(begin, _)| *begin)
|
|
69
|
+
.unwrap();
|
|
70
|
+
|
|
71
|
+
results.push(Match {
|
|
72
|
+
source_location: format!("{}:{}:1", file_path, begin_line),
|
|
73
|
+
comment_text: comment_text.to_string(),
|
|
74
|
+
});
|
|
75
|
+
} else if line[..pos].contains('#') {
|
|
76
|
+
// Single-line comment: # appears before REMIND_ME:
|
|
77
|
+
results.push(Match {
|
|
78
|
+
source_location: format!("{}:{}:1", file_path, line_num),
|
|
79
|
+
comment_text: comment_text.to_string(),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Otherwise: REMIND_ME: in code/string, skip it
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
results
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Walk a directory (or scan a single file) for REMIND_ME comments in .rb files.
|
|
90
|
+
/// Returns a Vec of (source_location, comment_text) tuples.
|
|
91
|
+
pub fn scan_for_remind_me_comments(path: &str) -> Vec<(String, String)> {
|
|
92
|
+
let p = Path::new(path);
|
|
93
|
+
|
|
94
|
+
if p.is_file() {
|
|
95
|
+
return scan_file(path)
|
|
96
|
+
.into_iter()
|
|
97
|
+
.map(|m| (m.source_location, m.comment_text))
|
|
98
|
+
.collect();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Directory mode: parallel walk with ignore crate
|
|
102
|
+
let results: Mutex<Vec<(String, String)>> = Mutex::new(Vec::new());
|
|
103
|
+
|
|
104
|
+
ignore::WalkBuilder::new(path)
|
|
105
|
+
.hidden(false)
|
|
106
|
+
.git_ignore(false)
|
|
107
|
+
.git_global(false)
|
|
108
|
+
.git_exclude(false)
|
|
109
|
+
.build_parallel()
|
|
110
|
+
.run(|| {
|
|
111
|
+
Box::new(|entry| {
|
|
112
|
+
if let Ok(entry) = entry {
|
|
113
|
+
let entry_path = entry.path();
|
|
114
|
+
if entry_path.is_file()
|
|
115
|
+
&& entry_path.extension().map_or(false, |ext| ext == "rb")
|
|
116
|
+
{
|
|
117
|
+
if let Some(path_str) = entry_path.to_str() {
|
|
118
|
+
let matches = scan_file(path_str);
|
|
119
|
+
if !matches.is_empty() {
|
|
120
|
+
let mut r = results.lock().unwrap();
|
|
121
|
+
for m in matches {
|
|
122
|
+
r.push((m.source_location, m.comment_text));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
ignore::WalkState::Continue
|
|
129
|
+
})
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
results.into_inner().unwrap()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[cfg(test)]
|
|
136
|
+
mod tests {
|
|
137
|
+
use super::*;
|
|
138
|
+
|
|
139
|
+
#[test]
|
|
140
|
+
fn test_single_line_comment() {
|
|
141
|
+
let content = "# some comment\n# REMIND_ME: { gem: 'rails' }\ncode here\n";
|
|
142
|
+
let tmp = std::env::temp_dir().join("test_single_line.rb");
|
|
143
|
+
fs::write(&tmp, content).unwrap();
|
|
144
|
+
let matches = scan_file(tmp.to_str().unwrap());
|
|
145
|
+
assert_eq!(matches.len(), 1);
|
|
146
|
+
assert!(matches[0].source_location.ends_with(":2:1"));
|
|
147
|
+
assert_eq!(matches[0].comment_text, " { gem: 'rails' }");
|
|
148
|
+
fs::remove_file(&tmp).ok();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[test]
|
|
152
|
+
fn test_block_comment() {
|
|
153
|
+
let content = "=begin\n some text\n REMIND_ME: { gem: 'rails' }\n=end\n";
|
|
154
|
+
let tmp = std::env::temp_dir().join("test_block_comment.rb");
|
|
155
|
+
fs::write(&tmp, content).unwrap();
|
|
156
|
+
let matches = scan_file(tmp.to_str().unwrap());
|
|
157
|
+
assert_eq!(matches.len(), 1);
|
|
158
|
+
assert!(matches[0].source_location.ends_with(":1:1")); // points to =begin
|
|
159
|
+
assert_eq!(matches[0].comment_text, " { gem: 'rails' }");
|
|
160
|
+
fs::remove_file(&tmp).ok();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn test_remind_me_in_string_skipped() {
|
|
165
|
+
let content = "puts \"REMIND_ME: something\"\n";
|
|
166
|
+
let tmp = std::env::temp_dir().join("test_in_string.rb");
|
|
167
|
+
fs::write(&tmp, content).unwrap();
|
|
168
|
+
let matches = scan_file(tmp.to_str().unwrap());
|
|
169
|
+
assert_eq!(matches.len(), 0);
|
|
170
|
+
fs::remove_file(&tmp).ok();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#[test]
|
|
174
|
+
fn test_no_matches() {
|
|
175
|
+
let content = "# just a comment\nputs 'hello'\n";
|
|
176
|
+
let tmp = std::env::temp_dir().join("test_no_match.rb");
|
|
177
|
+
fs::write(&tmp, content).unwrap();
|
|
178
|
+
let matches = scan_file(tmp.to_str().unwrap());
|
|
179
|
+
assert_eq!(matches.len(), 0);
|
|
180
|
+
fs::remove_file(&tmp).ok();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#[test]
|
|
184
|
+
fn test_multiple_matches() {
|
|
185
|
+
let content = "# REMIND_ME: { gem: 'a' }\n# regular\n# REMIND_ME: { gem: 'b' }\n";
|
|
186
|
+
let tmp = std::env::temp_dir().join("test_multiple.rb");
|
|
187
|
+
fs::write(&tmp, content).unwrap();
|
|
188
|
+
let matches = scan_file(tmp.to_str().unwrap());
|
|
189
|
+
assert_eq!(matches.len(), 2);
|
|
190
|
+
assert!(matches[0].source_location.ends_with(":1:1"));
|
|
191
|
+
assert!(matches[1].source_location.ends_with(":3:1"));
|
|
192
|
+
fs::remove_file(&tmp).ok();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#[test]
|
|
196
|
+
fn test_mixed_comment_types() {
|
|
197
|
+
let content = "# REMIND_ME: { gem: 'a' }\n=begin\n REMIND_ME: { gem: 'b' }\n=end\n";
|
|
198
|
+
let tmp = std::env::temp_dir().join("test_mixed.rb");
|
|
199
|
+
fs::write(&tmp, content).unwrap();
|
|
200
|
+
let matches = scan_file(tmp.to_str().unwrap());
|
|
201
|
+
assert_eq!(matches.len(), 2);
|
|
202
|
+
assert!(matches[0].source_location.ends_with(":1:1")); // single-line
|
|
203
|
+
assert!(matches[1].source_location.ends_with(":2:1")); // block, points to =begin
|
|
204
|
+
fs::remove_file(&tmp).ok();
|
|
205
|
+
}
|
|
206
|
+
}
|
data/lib/remind_me/runner.rb
CHANGED
|
@@ -10,6 +10,13 @@ require_relative 'reminder/generator'
|
|
|
10
10
|
require_relative 'utils/logger'
|
|
11
11
|
require_relative 'utils/result_printer'
|
|
12
12
|
|
|
13
|
+
begin
|
|
14
|
+
require 'remind_me/remind_me_native'
|
|
15
|
+
REMIND_ME_NATIVE_AVAILABLE = true
|
|
16
|
+
rescue LoadError
|
|
17
|
+
REMIND_ME_NATIVE_AVAILABLE = false
|
|
18
|
+
end
|
|
19
|
+
|
|
13
20
|
module RemindMe
|
|
14
21
|
class Runner
|
|
15
22
|
extend BailOut
|
|
@@ -33,6 +40,27 @@ module RemindMe
|
|
|
33
40
|
end
|
|
34
41
|
|
|
35
42
|
def self.collect_reminders(path)
|
|
43
|
+
if REMIND_ME_NATIVE_AVAILABLE
|
|
44
|
+
collect_reminders_native(path)
|
|
45
|
+
else
|
|
46
|
+
# :nocov:
|
|
47
|
+
collect_reminders_ruby(path)
|
|
48
|
+
# :nocov:
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.collect_reminders_native(path)
|
|
53
|
+
raw_comments = RemindMe::Native.scan_for_remind_me_comments(path)
|
|
54
|
+
return if raw_comments.empty?
|
|
55
|
+
|
|
56
|
+
Parallel.flat_map(in_groups(raw_comments, processor_count, false)) do |comments|
|
|
57
|
+
parser = silent_parser
|
|
58
|
+
comments.flat_map { |raw_comment| RemindMe::Reminder::Generator.generate(raw_comment[0], raw_comment[1], parser) }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# :nocov:
|
|
63
|
+
def self.collect_reminders_ruby(path)
|
|
36
64
|
files = relevant_ruby_files(path)
|
|
37
65
|
return if files.empty?
|
|
38
66
|
|
|
@@ -42,6 +70,7 @@ module RemindMe
|
|
|
42
70
|
raw_comments.flat_map { |raw_comment| RemindMe::Reminder::Generator.generate(raw_comment[0], raw_comment[1], parser) }
|
|
43
71
|
end
|
|
44
72
|
end
|
|
73
|
+
# :nocov:
|
|
45
74
|
|
|
46
75
|
def self.silent_parser
|
|
47
76
|
parser = Parser::CurrentRuby.new
|
|
@@ -65,6 +94,7 @@ module RemindMe
|
|
|
65
94
|
.map { |x| [x[0], x[1][1].split("\n").first] }
|
|
66
95
|
end
|
|
67
96
|
|
|
97
|
+
# :nocov:
|
|
68
98
|
def self.relevant_ruby_files(parse_path)
|
|
69
99
|
Parallel.flat_map(in_groups(collect_ruby_files(parse_path), processor_count, false)) do |files|
|
|
70
100
|
files.select do |file|
|
|
@@ -84,6 +114,7 @@ module RemindMe
|
|
|
84
114
|
end
|
|
85
115
|
files
|
|
86
116
|
end
|
|
117
|
+
# :nocov:
|
|
87
118
|
|
|
88
119
|
def self.in_groups(array, number, fill_with = nil)
|
|
89
120
|
division = array.size.div number
|
|
@@ -111,7 +142,9 @@ module RemindMe
|
|
|
111
142
|
|
|
112
143
|
private_class_method :in_groups,
|
|
113
144
|
:collect_ruby_files,
|
|
114
|
-
:collect_relevant_comments
|
|
145
|
+
:collect_relevant_comments,
|
|
146
|
+
:collect_reminders_native,
|
|
147
|
+
:collect_reminders_ruby
|
|
115
148
|
end
|
|
116
149
|
end
|
|
117
150
|
|
data/lib/remind_me/version.rb
CHANGED
data/remind_me.gemspec
CHANGED
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = spec.summary
|
|
13
13
|
spec.homepage = 'https://github.com/nikola-maric/remind_me'
|
|
14
14
|
spec.license = 'MIT'
|
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.
|
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
|
|
16
16
|
|
|
17
17
|
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
18
|
spec.metadata['source_code_uri'] = spec.homepage
|
|
@@ -26,8 +26,11 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
spec.bindir = 'exe'
|
|
27
27
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
28
28
|
spec.require_paths = ['lib']
|
|
29
|
+
spec.extensions = ['ext/remind_me/extconf.rb']
|
|
29
30
|
|
|
30
31
|
spec.add_development_dependency 'rake'
|
|
32
|
+
spec.add_development_dependency 'rake-compiler'
|
|
33
|
+
spec.add_development_dependency 'rb_sys'
|
|
31
34
|
spec.add_development_dependency 'rspec'
|
|
32
35
|
spec.add_development_dependency 'rubocop'
|
|
33
36
|
spec.add_development_dependency 'simplecov'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: remind_me
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nikola Marić
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-02-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -24,6 +24,34 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake-compiler
|
|
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: rb_sys
|
|
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'
|
|
27
55
|
- !ruby/object:Gem::Dependency
|
|
28
56
|
name: rspec
|
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -99,20 +127,28 @@ description: Processor of REMIND_ME comments in the code, reminding us to revisi
|
|
|
99
127
|
email:
|
|
100
128
|
- nkl.maric@gmail.net
|
|
101
129
|
executables: []
|
|
102
|
-
extensions:
|
|
130
|
+
extensions:
|
|
131
|
+
- ext/remind_me/extconf.rb
|
|
103
132
|
extra_rdoc_files: []
|
|
104
133
|
files:
|
|
105
134
|
- ".github/workflows/main.yml"
|
|
106
135
|
- ".gitignore"
|
|
107
136
|
- ".rspec"
|
|
108
137
|
- ".rubocop.yml"
|
|
138
|
+
- CLAUDE.md
|
|
109
139
|
- CODE_OF_CONDUCT.md
|
|
140
|
+
- Cargo.toml
|
|
110
141
|
- Gemfile
|
|
111
142
|
- LICENSE.txt
|
|
112
143
|
- README.md
|
|
113
144
|
- Rakefile
|
|
114
145
|
- bin/console
|
|
115
146
|
- bin/setup
|
|
147
|
+
- ext/remind_me/Cargo.toml
|
|
148
|
+
- ext/remind_me/extconf.rb
|
|
149
|
+
- ext/remind_me/src/bench.rs
|
|
150
|
+
- ext/remind_me/src/lib.rs
|
|
151
|
+
- ext/remind_me/src/scanner.rs
|
|
116
152
|
- lib/remind_me.rb
|
|
117
153
|
- lib/remind_me/bail_out.rb
|
|
118
154
|
- lib/remind_me/railtie.rb
|
|
@@ -137,7 +173,7 @@ metadata:
|
|
|
137
173
|
homepage_uri: https://github.com/nikola-maric/remind_me
|
|
138
174
|
source_code_uri: https://github.com/nikola-maric/remind_me
|
|
139
175
|
changelog_uri: https://github.com/nikola-maric/remind_me
|
|
140
|
-
post_install_message:
|
|
176
|
+
post_install_message:
|
|
141
177
|
rdoc_options: []
|
|
142
178
|
require_paths:
|
|
143
179
|
- lib
|
|
@@ -145,15 +181,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
145
181
|
requirements:
|
|
146
182
|
- - ">="
|
|
147
183
|
- !ruby/object:Gem::Version
|
|
148
|
-
version: 2.
|
|
184
|
+
version: 2.7.0
|
|
149
185
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
186
|
requirements:
|
|
151
187
|
- - ">="
|
|
152
188
|
- !ruby/object:Gem::Version
|
|
153
189
|
version: '0'
|
|
154
190
|
requirements: []
|
|
155
|
-
rubygems_version: 3.
|
|
156
|
-
signing_key:
|
|
191
|
+
rubygems_version: 3.4.14
|
|
192
|
+
signing_key:
|
|
157
193
|
specification_version: 4
|
|
158
194
|
summary: Processor of REMIND_ME comments in the code, reminding us to revisit parts
|
|
159
195
|
of code
|