smart_todo 1.4.3 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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.