smart_todo 1.3.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer.json +25 -0
- data/.github/workflows/build.yml +1 -2
- data/.github/workflows/cla.yml +22 -0
- data/.rubocop.yml +2 -0
- data/Gemfile.lock +20 -16
- data/LICENSE.txt +1 -1
- data/bin/rubocop +4 -2
- data/exe/smart_todo +1 -1
- data/lib/smart_todo/cli.rb +25 -2
- data/lib/smart_todo/dispatchers/base.rb +23 -21
- data/lib/smart_todo/dispatchers/output.rb +3 -1
- data/lib/smart_todo/dispatchers/slack.rb +6 -4
- data/lib/smart_todo/events/date.rb +14 -12
- data/lib/smart_todo/parser/metadata_parser.rb +13 -8
- data/lib/smart_todo/slack_client.rb +2 -1
- data/lib/smart_todo/version.rb +1 -1
- data/lib/smart_todo_cop.rb +27 -8
- data/service.yml +0 -1
- data/smart_todo.gemspec +2 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4daea874da20235bc9b20d315058644bafb73e78b14c6c266f6252f4b346bcf1
|
4
|
+
data.tar.gz: feb305070b73a03e2481515a914cca2fcaad264f3871cd65e242bf58ccb3612f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7fcba36662ce881114aafa3f5bba320d767c3e299a1d9d91f06a505107815d631c008ed87a1e853d1bb2766c1e9bdce756125ccc9c5ed65917264f962da62814
|
7
|
+
data.tar.gz: 981c7785a6c5e4d2805d004f6ec1411a953ec9d68005314bc8fe52bf1deb5f6c036a73ea4a08cf3346a120f765971c4fbb0049bdc4ebf97c867bf7e14f7690b8
|
@@ -0,0 +1,25 @@
|
|
1
|
+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
2
|
+
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
|
3
|
+
{
|
4
|
+
"name": "Ruby",
|
5
|
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
6
|
+
"image": "mcr.microsoft.com/devcontainers/ruby:0-3-bullseye",
|
7
|
+
"features": {
|
8
|
+
"ghcr.io/devcontainers/features/github-cli:1": {}
|
9
|
+
},
|
10
|
+
|
11
|
+
// Features to add to the dev container. More info: https://containers.dev/features.
|
12
|
+
// "features": {},
|
13
|
+
|
14
|
+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
15
|
+
// "forwardPorts": [],
|
16
|
+
|
17
|
+
// Use 'postCreateCommand' to run commands after the container is created.
|
18
|
+
"postCreateCommand": "bundle install",
|
19
|
+
|
20
|
+
// Configure tool-specific properties.
|
21
|
+
// "customizations": {},
|
22
|
+
|
23
|
+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
24
|
+
// "remoteUser": "root"
|
25
|
+
}
|
data/.github/workflows/build.yml
CHANGED
@@ -12,7 +12,7 @@ jobs:
|
|
12
12
|
name: Ruby ${{ matrix.version }}
|
13
13
|
strategy:
|
14
14
|
matrix:
|
15
|
-
version: [
|
15
|
+
version: [3.0, 3.1, 3.2]
|
16
16
|
|
17
17
|
steps:
|
18
18
|
- uses: actions/checkout@v2
|
@@ -24,4 +24,3 @@ jobs:
|
|
24
24
|
- name: Run Tests
|
25
25
|
run: |
|
26
26
|
bundle exec rake test
|
27
|
-
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: Contributor License Agreement (CLA)
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request_target:
|
5
|
+
types: [opened, synchronize]
|
6
|
+
issue_comment:
|
7
|
+
types: [created]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
cla:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
if: |
|
13
|
+
(github.event.issue.pull_request
|
14
|
+
&& !github.event.issue.pull_request.merged_at
|
15
|
+
&& contains(github.event.comment.body, 'signed')
|
16
|
+
)
|
17
|
+
|| (github.event.pull_request && !github.event.pull_request.merged)
|
18
|
+
steps:
|
19
|
+
- uses: Shopify/shopify-cla-action@v1
|
20
|
+
with:
|
21
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
22
|
+
cla-token: ${{ secrets.CLA_TOKEN }}
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
smart_todo (1.
|
4
|
+
smart_todo (1.5.0)
|
5
5
|
rexml
|
6
6
|
|
7
7
|
GEM
|
@@ -13,30 +13,34 @@ GEM
|
|
13
13
|
crack (0.4.5)
|
14
14
|
rexml
|
15
15
|
hashdiff (1.0.1)
|
16
|
+
json (2.6.3)
|
16
17
|
minitest (5.14.4)
|
17
|
-
parallel (1.
|
18
|
-
parser (3.
|
18
|
+
parallel (1.23.0)
|
19
|
+
parser (3.2.2.3)
|
19
20
|
ast (~> 2.4.1)
|
21
|
+
racc
|
20
22
|
public_suffix (4.0.6)
|
21
|
-
|
23
|
+
racc (1.7.0)
|
24
|
+
rainbow (3.1.1)
|
22
25
|
rake (13.0.6)
|
23
|
-
regexp_parser (2.
|
26
|
+
regexp_parser (2.8.1)
|
24
27
|
rexml (3.2.5)
|
25
|
-
rubocop (1.
|
28
|
+
rubocop (1.52.1)
|
29
|
+
json (~> 2.3)
|
26
30
|
parallel (~> 1.10)
|
27
|
-
parser (>= 3.
|
31
|
+
parser (>= 3.2.2.3)
|
28
32
|
rainbow (>= 2.2.2, < 4.0)
|
29
33
|
regexp_parser (>= 1.8, < 3.0)
|
30
|
-
rexml
|
31
|
-
rubocop-ast (>= 1.
|
34
|
+
rexml (>= 3.2.5, < 4.0)
|
35
|
+
rubocop-ast (>= 1.28.0, < 2.0)
|
32
36
|
ruby-progressbar (~> 1.7)
|
33
|
-
unicode-display_width (>=
|
34
|
-
rubocop-ast (1.
|
35
|
-
parser (>= 3.
|
36
|
-
rubocop-shopify (2.
|
37
|
-
rubocop (~> 1.
|
38
|
-
ruby-progressbar (1.
|
39
|
-
unicode-display_width (2.
|
37
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
38
|
+
rubocop-ast (1.29.0)
|
39
|
+
parser (>= 3.2.1.0)
|
40
|
+
rubocop-shopify (2.14.0)
|
41
|
+
rubocop (~> 1.51)
|
42
|
+
ruby-progressbar (1.13.0)
|
43
|
+
unicode-display_width (2.4.2)
|
40
44
|
webmock (3.11.2)
|
41
45
|
addressable (>= 2.3.6)
|
42
46
|
crack (>= 0.3.2)
|
data/LICENSE.txt
CHANGED
data/bin/rubocop
CHANGED
@@ -9,8 +9,10 @@
|
|
9
9
|
#
|
10
10
|
|
11
11
|
require "pathname"
|
12
|
-
ENV["BUNDLE_GEMFILE"] ||= File.expand_path(
|
13
|
-
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path(
|
13
|
+
"../../Gemfile",
|
14
|
+
Pathname.new(__FILE__).realpath,
|
15
|
+
)
|
14
16
|
|
15
17
|
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
18
|
|
data/exe/smart_todo
CHANGED
data/lib/smart_todo/cli.rb
CHANGED
@@ -9,21 +9,35 @@ module SmartTodo
|
|
9
9
|
class CLI
|
10
10
|
def initialize
|
11
11
|
@options = {}
|
12
|
+
@errors = []
|
12
13
|
end
|
13
14
|
|
14
15
|
# @param args [Array<String>]
|
15
16
|
def run(args = ARGV)
|
16
17
|
paths = define_options.parse!(args)
|
17
18
|
validate_options!
|
19
|
+
|
18
20
|
paths << "." if paths.empty?
|
19
21
|
|
20
22
|
paths.each do |path|
|
21
23
|
normalize_path(path).each do |file|
|
22
24
|
parse_file(file)
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
+
$stdout.print(".")
|
27
|
+
$stdout.flush
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if @errors.empty?
|
32
|
+
0
|
33
|
+
else
|
34
|
+
$stderr.puts "There were errors while checking for TODOs:\n"
|
35
|
+
|
36
|
+
@errors.each do |error|
|
37
|
+
$stderr.puts error
|
26
38
|
end
|
39
|
+
|
40
|
+
1
|
27
41
|
end
|
28
42
|
end
|
29
43
|
|
@@ -71,8 +85,17 @@ module SmartTodo
|
|
71
85
|
event_message = nil
|
72
86
|
event_met = todo_node.metadata.events.find do |event|
|
73
87
|
event_message = Events.public_send(event.method_name, *event.arguments)
|
88
|
+
rescue => e
|
89
|
+
message = "Error while parsing #{file} on event `#{event.method_name}` with arguments #{event.arguments}: " \
|
90
|
+
"#{e.message}"
|
91
|
+
|
92
|
+
@errors << message
|
93
|
+
|
94
|
+
nil
|
74
95
|
end
|
75
96
|
|
97
|
+
@errors.concat(todo_node.metadata.errors)
|
98
|
+
|
76
99
|
dispatcher.new(event_message, todo_node, file, @options).dispatch if event_met
|
77
100
|
end
|
78
101
|
end
|
@@ -3,29 +3,31 @@
|
|
3
3
|
module SmartTodo
|
4
4
|
module Dispatchers
|
5
5
|
class Base
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
class << self
|
7
|
+
# Factory pattern to retrieve the right dispatcher class.
|
8
|
+
#
|
9
|
+
# @param dispatcher [String]
|
10
|
+
#
|
11
|
+
# @return [Class]
|
12
|
+
def class_for(dispatcher)
|
13
|
+
case dispatcher
|
14
|
+
when "slack"
|
15
|
+
Slack
|
16
|
+
when nil, "output"
|
17
|
+
Output
|
18
|
+
end
|
17
19
|
end
|
18
|
-
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
# Subclasses should define what options from the CLI they need in order
|
22
|
+
# to properly deliver the message. For instance the Slack dispatcher
|
23
|
+
# requires an API key.
|
24
|
+
#
|
25
|
+
# @param _options [Hash]
|
26
|
+
#
|
27
|
+
# @return void
|
28
|
+
def validate_options!(_options)
|
29
|
+
raise(NotImplemetedError, "subclass responsability")
|
30
|
+
end
|
29
31
|
end
|
30
32
|
|
31
33
|
# @param event_message [String] the success message associated
|
@@ -5,10 +5,12 @@ module SmartTodo
|
|
5
5
|
# Dispatcher that sends TODO reminders on Slack. Assignees can be either individual
|
6
6
|
# (using the associated slack email address) or a channel.
|
7
7
|
class Slack < Base
|
8
|
-
|
9
|
-
|
8
|
+
class << self
|
9
|
+
def validate_options!(options)
|
10
|
+
options[:slack_token] ||= ENV.fetch("SMART_TODO_SLACK_TOKEN") { raise(ArgumentError, "Missing :slack_token") }
|
10
11
|
|
11
|
-
|
12
|
+
options.fetch(:fallback_channel) { raise(ArgumentError, "Missing :fallback_channel") }
|
13
|
+
end
|
12
14
|
end
|
13
15
|
|
14
16
|
# Make a Slack API call to dispatch the message to each assignee
|
@@ -35,7 +37,7 @@ module SmartTodo
|
|
35
37
|
|
36
38
|
client.post_message(user.dig("user", "id"), slack_message(user, assignee))
|
37
39
|
rescue SlackClient::Error => error
|
38
|
-
if ["users_not_found", "channel_not_found"].include?(error.error_code)
|
40
|
+
if ["users_not_found", "channel_not_found", "is_archived"].include?(error.error_code)
|
39
41
|
user = { "user" => { "id" => @options[:fallback_channel] }, "fallback" => true }
|
40
42
|
else
|
41
43
|
raise(error)
|
@@ -6,20 +6,22 @@ module SmartTodo
|
|
6
6
|
module Events
|
7
7
|
# An event that check if the passed date is passed
|
8
8
|
class Date
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
class << self
|
10
|
+
# @param on_date [String] a string parsable by Time.parse
|
11
|
+
# @return [String, false]
|
12
|
+
def met?(on_date)
|
13
|
+
if Time.now >= Time.parse(on_date)
|
14
|
+
message(on_date)
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
16
18
|
end
|
17
|
-
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
# @param on_date [String]
|
21
|
+
# @return [String]
|
22
|
+
def message(on_date)
|
23
|
+
"We are past the *#{on_date}* due date and your TODO is now ready to be addressed."
|
24
|
+
end
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -18,10 +18,12 @@ module SmartTodo
|
|
18
18
|
|
19
19
|
# This class is used to parse the ruby TODO() comment.
|
20
20
|
class MetadataParser < Ripper
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
class << self
|
22
|
+
# @param source [String] the actual Ruby code
|
23
|
+
def parse(source)
|
24
|
+
sexp = new(source).parse
|
25
|
+
Visitor.new.tap { |v| v.process(sexp) }
|
26
|
+
end
|
25
27
|
end
|
26
28
|
|
27
29
|
# @return [Array] an Array of Array
|
@@ -78,11 +80,12 @@ module SmartTodo
|
|
78
80
|
end
|
79
81
|
|
80
82
|
class Visitor
|
81
|
-
attr_reader :events, :assignees
|
83
|
+
attr_reader :events, :assignees, :errors
|
82
84
|
|
83
85
|
def initialize
|
84
86
|
@events = []
|
85
87
|
@assignees = []
|
88
|
+
@errors = []
|
86
89
|
end
|
87
90
|
|
88
91
|
# Iterate over each tokens returned from the parser and call
|
@@ -104,9 +107,11 @@ module SmartTodo
|
|
104
107
|
# @param method_node [MethodNode]
|
105
108
|
# @return [void]
|
106
109
|
def on_todo_event(method_node)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
+
if method_node.is_a?(MethodNode)
|
111
|
+
events << method_node
|
112
|
+
else
|
113
|
+
errors << "Incorrect `:on` event format: #{method_node}"
|
114
|
+
end
|
110
115
|
end
|
111
116
|
|
112
117
|
# @param assignee [String]
|
@@ -82,7 +82,7 @@ module SmartTodo
|
|
82
82
|
slack_response!(response)
|
83
83
|
end
|
84
84
|
|
85
|
-
#
|
85
|
+
# Check if the response to Slack was a 200 and the Slack API request was successful
|
86
86
|
#
|
87
87
|
# @param response [Net::HTTPResponse] a net Net::HTTPResponse subclass
|
88
88
|
# (Net::HTTPOK, Net::HTTPNotFound ...)
|
@@ -92,6 +92,7 @@ module SmartTodo
|
|
92
92
|
# @raise [SlackClient::Error] in case Slack returns a { ok: false } in the body
|
93
93
|
def slack_response!(response)
|
94
94
|
raise(Net::HTTPError.new("Request to slack failed", response)) unless response.code_type < Net::HTTPSuccess
|
95
|
+
|
95
96
|
body = JSON.parse(response.body)
|
96
97
|
|
97
98
|
if body["ok"]
|
data/lib/smart_todo/version.rb
CHANGED
data/lib/smart_todo_cop.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "smart_todo
|
3
|
+
require "smart_todo"
|
4
4
|
|
5
5
|
module RuboCop
|
6
6
|
module Cop
|
@@ -10,29 +10,48 @@ module RuboCop
|
|
10
10
|
#
|
11
11
|
# @see https://rubocop.readthedocs.io/en/latest/extensions/#loading-extensions
|
12
12
|
class SmartTodoCop < Cop
|
13
|
-
|
14
|
-
|
13
|
+
HELP = "For more info please look at https://github.com/Shopify/smart_todo/wiki/Syntax"
|
14
|
+
MSG = "Don't write regular TODO comments. Write SmartTodo compatible syntax comments. #{HELP}"
|
15
15
|
|
16
16
|
# @param processed_source [RuboCop::ProcessedSource]
|
17
17
|
# @return [void]
|
18
18
|
def investigate(processed_source)
|
19
19
|
processed_source.comments.each do |comment|
|
20
20
|
next unless /^#\sTODO/ =~ comment.text
|
21
|
-
next if smart_todo?(comment.text)
|
22
21
|
|
23
|
-
|
22
|
+
metadata = metadata(comment.text)
|
23
|
+
|
24
|
+
if metadata.errors.any?
|
25
|
+
add_offense(comment, message: "Invalid TODO format: #{metadata.errors.join(", ")}. #{HELP}")
|
26
|
+
elsif !smart_todo?(metadata)
|
27
|
+
add_offense(comment)
|
28
|
+
elsif (methods = invalid_event_methods(metadata.events)).any?
|
29
|
+
add_offense(comment, message: "Invalid event method(s): #{methods.join(", ")}. #{HELP}")
|
30
|
+
end
|
24
31
|
end
|
25
32
|
end
|
26
33
|
|
34
|
+
private
|
35
|
+
|
27
36
|
# @param comment [String]
|
28
|
-
# @return [
|
29
|
-
def
|
30
|
-
|
37
|
+
# @return [SmartTodo::Parser::Visitor]
|
38
|
+
def metadata(comment)
|
39
|
+
::SmartTodo::Parser::MetadataParser.parse(comment.gsub(/^#/, ""))
|
40
|
+
end
|
31
41
|
|
42
|
+
# @param metadata [SmartTodo::Parser::Visitor]
|
43
|
+
# @return [true, false]
|
44
|
+
def smart_todo?(metadata)
|
32
45
|
metadata.events.any? &&
|
33
46
|
metadata.events.all? { |event| event.is_a?(::SmartTodo::Parser::MethodNode) } &&
|
34
47
|
metadata.assignees.any?
|
35
48
|
end
|
49
|
+
|
50
|
+
# @param metadata [Array<SmartTodo::Parser::MethodNode>]
|
51
|
+
# @return [Array<String>]
|
52
|
+
def invalid_event_methods(events)
|
53
|
+
events.map(&:method_name).reject { |method| ::SmartTodo::Events.respond_to?(method) }
|
54
|
+
end
|
36
55
|
end
|
37
56
|
end
|
38
57
|
end
|
data/service.yml
CHANGED
@@ -1 +0,0 @@
|
|
1
|
-
classification: library
|
data/smart_todo.gemspec
CHANGED
@@ -24,6 +24,8 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.metadata["changelog_uri"] = spec.homepage + "/releases"
|
25
25
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
26
26
|
|
27
|
+
spec.required_ruby_version = ">= 3.0.0"
|
28
|
+
|
27
29
|
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
28
30
|
%x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
29
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_todo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rexml
|
@@ -91,7 +91,9 @@ executables:
|
|
91
91
|
extensions: []
|
92
92
|
extra_rdoc_files: []
|
93
93
|
files:
|
94
|
+
- ".devcontainer/devcontainer.json"
|
94
95
|
- ".github/workflows/build.yml"
|
96
|
+
- ".github/workflows/cla.yml"
|
95
97
|
- ".github/workflows/rubocop.yml"
|
96
98
|
- ".gitignore"
|
97
99
|
- ".rubocop.yml"
|
@@ -141,14 +143,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
143
|
requirements:
|
142
144
|
- - ">="
|
143
145
|
- !ruby/object:Gem::Version
|
144
|
-
version:
|
146
|
+
version: 3.0.0
|
145
147
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
148
|
requirements:
|
147
149
|
- - ">="
|
148
150
|
- !ruby/object:Gem::Version
|
149
151
|
version: '0'
|
150
152
|
requirements: []
|
151
|
-
rubygems_version: 3.
|
153
|
+
rubygems_version: 3.4.13
|
152
154
|
signing_key:
|
153
155
|
specification_version: 4
|
154
156
|
summary: Enhance todo's comments in your codebase.
|