smart_todo 1.4.3 → 1.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b21d53a3957ea19430b3726fd82fefeceef262330ead8eb9ef1ef087acd29bb4
4
- data.tar.gz: 3927e36c7c059e90613ceec82e8d00efc8c7e6ceceb6183d05a5cd59c466086c
3
+ metadata.gz: c17d1471d893e5a764dda2fa9adcfc73049b5c5a45d6678feca40104879065fc
4
+ data.tar.gz: 85411f13bc189f060372e90a72fc1a545cd0030b6f67cf0fd75f1ff97dbeb755
5
5
  SHA512:
6
- metadata.gz: f92dd70f82426b84d8a651a9963dffbf6b66017c484b25371b68c16495bf2cdb28faba9fdb31a0ab56ffbb5f848f1a877375379c4c94ca946bb16d9630d7d075
7
- data.tar.gz: 69aa57c5583a6adba8320783171eeaef3814e9e758c81b3c81bbd455c3d238d72d145be44c5fa6732764d7561beac2c19181f8344462f209346572bb23f6bf3d
6
+ metadata.gz: bfe190ce26c9b3e65aea86f3d23e40a400c14edcac8c465b361341ac00a7ab7d59498c9c3edda92d7e180659cad96240148bce26dc3a1db77a92ee2e03575204
7
+ data.tar.gz: f1c0386fbb5346a74c3f9c0c3b6228a9af29a810241793a5f44db5cbe7d7873c24e9dda987b65aff0d4984193be3652bec472431a48407badb2eb7610da506d6
@@ -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
+ }
@@ -12,7 +12,7 @@ jobs:
12
12
  name: Ruby ${{ matrix.version }}
13
13
  strategy:
14
14
  matrix:
15
- version: [2.5, 2.6, 2.7, 3.0]
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
-
data/.rubocop.yml CHANGED
@@ -3,5 +3,7 @@ inherit_gem:
3
3
 
4
4
  AllCops:
5
5
  TargetRubyVersion: 3.0
6
+ NewCops: disable
7
+ SuggestExtensions: false
6
8
  Exclude:
7
9
  - vendor/**/*
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- smart_todo (1.4.3)
4
+ smart_todo (1.6.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.21.0)
18
- parser (3.0.2.0)
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
- rainbow (3.0.0)
23
+ racc (1.7.0)
24
+ rainbow (3.1.1)
22
25
  rake (13.0.6)
23
- regexp_parser (2.1.1)
26
+ regexp_parser (2.8.1)
24
27
  rexml (3.2.5)
25
- rubocop (1.23.0)
28
+ rubocop (1.52.1)
29
+ json (~> 2.3)
26
30
  parallel (~> 1.10)
27
- parser (>= 3.0.0.0)
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.12.0, < 2.0)
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 (>= 1.4.0, < 3.0)
34
- rubocop-ast (1.13.0)
35
- parser (>= 3.0.1.1)
36
- rubocop-shopify (2.3.0)
37
- rubocop (~> 1.22)
38
- ruby-progressbar (1.11.0)
39
- unicode-display_width (2.1.0)
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/bin/rubocop CHANGED
@@ -9,8 +9,10 @@
9
9
  #
10
10
 
11
11
  require "pathname"
12
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
- Pathname.new(__FILE__).realpath)
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
@@ -9,4 +9,4 @@ if ENV["ENABLE_SMART_TODO"] && !ARGV.include?("--dispatcher")
9
9
  ARGV << "--dispatcher" << "slack"
10
10
  end
11
11
 
12
- SmartTodo::CLI.new.run
12
+ exit SmartTodo::CLI.new.run
@@ -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
- STDOUT.print(".")
25
- STDOUT.flush
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
- # Factory pattern to retrive the right dispatcher class.
7
- #
8
- # @param dispatcher [String]
9
- #
10
- # @return [Class]
11
- def self.class_for(dispatcher)
12
- case dispatcher
13
- when "slack"
14
- Slack
15
- when nil, "output"
16
- Output
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
- # Subclasses should define what options from the CLI they need in order
21
- # to properly deliver the message. For instance the Slack dispatcher
22
- # requires an API key.
23
- #
24
- # @param _options [Hash]
25
- #
26
- # @return void
27
- def self.validate_options!(_options)
28
- raise(NotImplemetedError, "subclass responsability")
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
@@ -4,7 +4,9 @@ module SmartTodo
4
4
  module Dispatchers
5
5
  # A simple dispatcher that will output the reminder.
6
6
  class Output < Base
7
- def self.validate_options!(_); end
7
+ class << self
8
+ def validate_options!(_); end
9
+ end
8
10
 
9
11
  # @return void
10
12
  def dispatch
@@ -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
- def self.validate_options!(options)
9
- options[:slack_token] ||= ENV.fetch("SMART_TODO_SLACK_TOKEN") { raise(ArgumentError, "Missing :slack_token") }
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
- options.fetch(:fallback_channel) { raise(ArgumentError, "Missing :fallback_channel") }
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
- # @param on_date [String] a string parsable by Time.parse
10
- # @return [String, false]
11
- def self.met?(on_date)
12
- if Time.now >= Time.parse(on_date)
13
- message(on_date)
14
- else
15
- false
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
- # @param on_date [String]
20
- # @return [String]
21
- def self.message(on_date)
22
- "We are past the *#{on_date}* due date and your TODO is now ready to be addressed."
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
@@ -11,6 +11,11 @@ module SmartTodo
11
11
  # If the Pull Request or Issue is on a private repository, exporting a token
12
12
  # with the `repos` scope in the +SMART_TODO_GITHUB_TOKEN+ environment variable
13
13
  # is required.
14
+ #
15
+ # You can also set a per-org or per-repo token by exporting more specific environment variables:
16
+ # +SMART_TODO_GITHUB_TOKEN__<ORG>+ and +SMART_TODO_GITHUB_TOKEN__<ORG>__<REPO>+
17
+ # The +<ORG>+ and +<REPO>+ parts should be uppercased and use underscores.
18
+ # For example, +Shopify/my-repo+ would become +SMART_TODO_GITHUB_TOKEN__SHOPIFY__MY_REPO=...+.
14
19
  class IssueClose
15
20
  TOKEN_ENV = "SMART_TODO_GITHUB_TOKEN"
16
21
 
@@ -78,8 +83,30 @@ module SmartTodo
78
83
  # @return [Hash]
79
84
  def default_headers
80
85
  { "Accept" => "application/vnd.github.v3+json" }.tap do |headers|
81
- headers["Authorization"] = "token #{ENV[TOKEN_ENV]}" if ENV[TOKEN_ENV]
86
+ token = authorization_token
87
+ headers["Authorization"] = "token #{token}" if token
88
+ end
89
+ end
90
+
91
+ # @return [String, nil]
92
+ def authorization_token
93
+ # Will look in order for:
94
+ # SMART_TODO_GITHUB_TOKEN__ORG__REPO
95
+ # SMART_TODO_GITHUB_TOKEN__ORG
96
+ # SMART_TODO_GITHUB_TOKEN
97
+ parts = [
98
+ TOKEN_ENV,
99
+ @organization.upcase.gsub(/[^A-Z0-9]/, "_"),
100
+ @repo.upcase.gsub(/[^A-Z0-9]/, "_"),
101
+ ]
102
+
103
+ (parts.size - 1).downto(0).each do |i|
104
+ key = parts[0..i].join("__")
105
+ token = ENV[key]
106
+ return token unless token.nil? || token.empty?
82
107
  end
108
+
109
+ nil
83
110
  end
84
111
  end
85
112
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartTodo
4
+ module Events
5
+ # An event that checks the currently installed ruby version.
6
+ # @example
7
+ # RubyVersion.new(['>= 2.3', '< 3'])
8
+ class RubyVersion
9
+ def initialize(requirements)
10
+ @requirements = Gem::Requirement.new(requirements)
11
+ end
12
+
13
+ # @param requirements [Array<String>] a list of version specifiers
14
+ # @return [String, false]
15
+ def met?
16
+ if @requirements.satisfied_by?(Gem::Version.new(installed_ruby_version))
17
+ message(installed_ruby_version)
18
+ else
19
+ false
20
+ end
21
+ end
22
+
23
+ # @param installed_ruby_version [String], requirements [String]
24
+ # @return [String]
25
+ def message(installed_ruby_version)
26
+ "The currently installed version of Ruby #{installed_ruby_version} is #{@requirements}."
27
+ end
28
+
29
+ private
30
+
31
+ def installed_ruby_version
32
+ RUBY_VERSION
33
+ end
34
+ end
35
+ end
36
+ end
@@ -66,5 +66,13 @@ module SmartTodo
66
66
  def pull_request_close(organization, repo, pr_number)
67
67
  IssueClose.new(organization, repo, pr_number, type: "pulls").met?
68
68
  end
69
+
70
+ # Check if the installed ruby version meets requirements.
71
+ #
72
+ # @param requirements [Array<String>] a list of version specifiers
73
+ # @return [false, String]
74
+ def ruby_version(*requirements)
75
+ RubyVersion.new(requirements).met?
76
+ end
69
77
  end
70
78
  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
- # @param source [String] the actual Ruby code
22
- def self.parse(source)
23
- sexp = new(source).parse
24
- Visitor.new.tap { |v| v.process(sexp) }
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
@@ -35,7 +37,7 @@ module SmartTodo
35
37
  # @param args [Array]
36
38
  # @return [Array, MethodNode]
37
39
  def on_method_add_arg(method, args)
38
- if method == "TODO"
40
+ if method.start_with?(/TODO\W?/)
39
41
  args
40
42
  else
41
43
  MethodNode.new(method, args)
@@ -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
- return unless method_node.is_a?(MethodNode)
108
-
109
- events << method_node
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
- # Chech if the response to Slack was a 200 and the Slack API request was successful
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"]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SmartTodo
4
- VERSION = "1.4.3"
4
+ VERSION = "1.6.0"
5
5
  end
data/lib/smart_todo.rb CHANGED
@@ -18,6 +18,7 @@ module SmartTodo
18
18
  autoload :GemBump, "smart_todo/events/gem_bump"
19
19
  autoload :GemRelease, "smart_todo/events/gem_release"
20
20
  autoload :IssueClose, "smart_todo/events/issue_close"
21
+ autoload :RubyVersion, "smart_todo/events/ruby_version"
21
22
  end
22
23
 
23
24
  module Dispatchers
@@ -18,8 +18,12 @@ module RuboCop
18
18
  def investigate(processed_source)
19
19
  processed_source.comments.each do |comment|
20
20
  next unless /^#\sTODO/ =~ comment.text
21
+
21
22
  metadata = metadata(comment.text)
22
- if !smart_todo?(metadata)
23
+
24
+ if metadata.errors.any?
25
+ add_offense(comment, message: "Invalid TODO format: #{metadata.errors.join(", ")}. #{HELP}")
26
+ elsif !smart_todo?(metadata)
23
27
  add_offense(comment)
24
28
  elsif (methods = invalid_event_methods(metadata.events)).any?
25
29
  add_offense(comment, message: "Invalid event method(s): #{methods.join(", ")}. #{HELP}")
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.3
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-10 00:00:00.000000000 Z
11
+ date: 2023-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rexml
@@ -91,6 +91,7 @@ executables:
91
91
  extensions: []
92
92
  extra_rdoc_files: []
93
93
  files:
94
+ - ".devcontainer/devcontainer.json"
94
95
  - ".github/workflows/build.yml"
95
96
  - ".github/workflows/cla.yml"
96
97
  - ".github/workflows/rubocop.yml"
@@ -117,6 +118,7 @@ files:
117
118
  - lib/smart_todo/events/gem_bump.rb
118
119
  - lib/smart_todo/events/gem_release.rb
119
120
  - lib/smart_todo/events/issue_close.rb
121
+ - lib/smart_todo/events/ruby_version.rb
120
122
  - lib/smart_todo/parser/comment_parser.rb
121
123
  - lib/smart_todo/parser/metadata_parser.rb
122
124
  - lib/smart_todo/parser/todo_node.rb
@@ -142,14 +144,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
144
  requirements:
143
145
  - - ">="
144
146
  - !ruby/object:Gem::Version
145
- version: '0'
147
+ version: 3.0.0
146
148
  required_rubygems_version: !ruby/object:Gem::Requirement
147
149
  requirements:
148
150
  - - ">="
149
151
  - !ruby/object:Gem::Version
150
152
  version: '0'
151
153
  requirements: []
152
- rubygems_version: 3.3.3
154
+ rubygems_version: 3.4.16
153
155
  signing_key:
154
156
  specification_version: 4
155
157
  summary: Enhance todo's comments in your codebase.