smart_todo 1.0.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.
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+
6
+ module SmartTodo
7
+ # A simple client around the Slack API.
8
+ #
9
+ # @example Sending a message to a user.
10
+ # SmartTodo::SlackClient.new.post_message('#general', 'Hello!')
11
+ class SlackClient
12
+ # A generic error class raised when the Slack API returns back a 200
13
+ # but there was a problem (permission issues ...)
14
+ class Error < StandardError
15
+ attr_reader :error_code
16
+
17
+ # @param response_body [Hash] the parsed response body from Slack
18
+ def initialize(response_body)
19
+ @error_code = response_body['error']
20
+
21
+ super("Response body: #{response_body}")
22
+ end
23
+ end
24
+
25
+ # @param slack_token [String]
26
+ def initialize(slack_token)
27
+ @slack_token = slack_token
28
+ @client = Net::HTTP.new('slack.com', Net::HTTP.https_default_port).tap do |client|
29
+ client.use_ssl = true
30
+ client.read_timeout = 30
31
+ client.ssl_timeout = 15
32
+ end
33
+ end
34
+
35
+ # Retrieve the Slack ID of a user from his email
36
+ #
37
+ # @param email [String]
38
+ # @return [Hash]
39
+ #
40
+ # @raise [Net::HTTPError] in case the reques to Slack failed
41
+ # @raise [SlackClient::Error] in case Slack returs a { ok: false } in the body
42
+ #
43
+ # @see https://api.slack.com/methods/users.lookupByEmail
44
+ def lookup_user_by_email(email)
45
+ headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
46
+
47
+ request(:get, "/api/users.lookupByEmail?email=#{email}", nil, headers)
48
+ end
49
+
50
+ # Send a message to a Slack channel or to a user
51
+ #
52
+ # @param channel [String] The Slack channel or the user ID
53
+ # @param text [String] The message to send
54
+ # @return [Hash]
55
+ #
56
+ # @raise [Net::HTTPError] in case the reques to Slack failed
57
+ # @raise [SlackClient::Error] in case Slack returs a { ok: false } in the body
58
+ #
59
+ # @see https://api.slack.com/methods/chat.postMessage
60
+ def post_message(channel, text)
61
+ request(:post, '/api/chat.postMessage', JSON.dump(channel: channel, text: text))
62
+ end
63
+
64
+ private
65
+
66
+ # @param method [Symbol]
67
+ # @param endpoint [String]
68
+ # @param data [String] JSON encoded data when making a POST request
69
+ # @param headers [Hash]
70
+ #
71
+ # @raise [Net::HTTPError] in case the reques to Slack failed
72
+ # @raise [SlackClient::Error] in case Slack returs a { ok: false } in the body
73
+ def request(method, endpoint, data = nil, headers = {})
74
+ response = case method
75
+ when :post, :patch
76
+ @client.public_send(method, endpoint, data, default_headers.merge(headers))
77
+ else
78
+ @client.public_send(method, endpoint, default_headers.merge(headers))
79
+ end
80
+
81
+ slack_response!(response)
82
+ end
83
+
84
+ # Chech if the response to Slack was a 200 and the Slack API request was successful
85
+ #
86
+ # @param response [Net::HTTPResponse] a net Net::HTTPResponse subclass
87
+ # (Net::HTTPOK, Net::HTTPNotFound ...)
88
+ # @return [Hash]
89
+ #
90
+ # @raise [Net::HTTPError] in case the reques to Slack failed
91
+ # @raise [SlackClient::Error] in case Slack returs a { ok: false } in the body
92
+ def slack_response!(response)
93
+ raise(Net::HTTPError.new('Request to slack failed', response)) unless response.code_type < Net::HTTPSuccess
94
+ body = JSON.parse(response.body)
95
+
96
+ if body['ok']
97
+ body
98
+ else
99
+ raise(Error, body)
100
+ end
101
+ end
102
+
103
+ # The default headers required by Slack
104
+ #
105
+ # @return [Hash]
106
+ def default_headers
107
+ {
108
+ 'Content-Type' => 'application/json; charset=utf8',
109
+ 'Authorization' => "Bearer #{@slack_token}",
110
+ }
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartTodo
4
+ VERSION = "1.0.0"
5
+ end
data/lib/smart_todo.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "smart_todo/version"
4
+ require "smart_todo/events"
5
+
6
+ module SmartTodo
7
+ autoload :SlackClient, 'smart_todo/slack_client'
8
+ autoload :CLI, 'smart_todo/cli'
9
+ autoload :Dispatcher, 'smart_todo/dispatcher'
10
+
11
+ module Parser
12
+ autoload :CommentParser, 'smart_todo/parser/comment_parser'
13
+ autoload :TodoNode, 'smart_todo/parser/todo_node'
14
+ autoload :MetadataParser, 'smart_todo/parser/metadata_parser'
15
+ end
16
+
17
+ module Events
18
+ autoload :Date, 'smart_todo/events/date'
19
+ autoload :GemRelease, 'smart_todo/events/gem_release'
20
+ autoload :PullRequestClose, 'smart_todo/events/pull_request_close'
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'smart_todo/parser/metadata_parser'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module SmartTodo
8
+ # A RuboCop used to restrict the usage of regular TODO comments in code.
9
+ # This Cop does not run by default. It should be added to the RuboCop host's configuration file.
10
+ #
11
+ # @see https://rubocop.readthedocs.io/en/latest/extensions/#loading-extensions
12
+ class SmartTodoCop < Cop
13
+ MSG = "Don't write regular TODO comments. Write SmartTodo compatible syntax comments." \
14
+ "For more info please look at https://github.com/shopify/smart_todo"
15
+
16
+ # @param processed_source [RuboCop::ProcessedSource]
17
+ # @return [void]
18
+ def investigate(processed_source)
19
+ processed_source.comments.each do |comment|
20
+ next unless /^#\sTODO/ =~ comment.text
21
+ next if smart_todo?(comment.text)
22
+
23
+ add_offense(comment)
24
+ end
25
+ end
26
+
27
+ # @param comment [String]
28
+ # @return [true, false]
29
+ def smart_todo?(comment)
30
+ metadata = ::SmartTodo::Parser::MetadataParser.parse(comment.gsub(/^#/, ''))
31
+
32
+ metadata.events.any?
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1 @@
1
+ # using the default shipit config
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "smart_todo/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "smart_todo"
9
+ spec.version = SmartTodo::VERSION
10
+ spec.authors = ["Shopify"]
11
+ spec.email = ["rails@shopify.com"]
12
+
13
+ spec.summary = "Enhance todo's comments in your codebase."
14
+ spec.description = <<~EOM
15
+ SmartTodo is a tool designed to assign specific users on todo's task
16
+ written in your codebase and help assignees be reminded when it's time to commit
17
+ to their todo's.
18
+ EOM
19
+ spec.homepage = "https://github.com/shopify/smart_todo"
20
+ spec.license = "MIT"
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = spec.homepage
24
+ spec.metadata["changelog_uri"] = spec.homepage + "/blob/master/CHANGELOG.md"
25
+
26
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
27
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = ['smart_todo']
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency("bundler", "~> 1.17")
34
+ spec.add_development_dependency("rake", "~> 10.0")
35
+ spec.add_development_dependency("minitest", "~> 5.0")
36
+ spec.add_development_dependency("webmock")
37
+ spec.add_development_dependency("rubocop")
38
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smart_todo
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Shopify
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
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: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: |
84
+ SmartTodo is a tool designed to assign specific users on todo's task
85
+ written in your codebase and help assignees be reminded when it's time to commit
86
+ to their todo's.
87
+ email:
88
+ - rails@shopify.com
89
+ executables:
90
+ - smart_todo
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".gitignore"
95
+ - ".rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml"
96
+ - ".rubocop.yml"
97
+ - ".shopify-build/VERSION"
98
+ - ".shopify-build/smart-todo.yml"
99
+ - CODE_OF_CONDUCT.md
100
+ - Gemfile
101
+ - Gemfile.lock
102
+ - LICENSE.txt
103
+ - README.md
104
+ - Rakefile
105
+ - bin/console
106
+ - bin/rubocop
107
+ - bin/setup
108
+ - dev.yml
109
+ - exe/smart_todo
110
+ - lib/smart_todo.rb
111
+ - lib/smart_todo/cli.rb
112
+ - lib/smart_todo/dispatcher.rb
113
+ - lib/smart_todo/events.rb
114
+ - lib/smart_todo/events/date.rb
115
+ - lib/smart_todo/events/gem_release.rb
116
+ - lib/smart_todo/events/pull_request_close.rb
117
+ - lib/smart_todo/parser/comment_parser.rb
118
+ - lib/smart_todo/parser/metadata_parser.rb
119
+ - lib/smart_todo/parser/todo_node.rb
120
+ - lib/smart_todo/slack_client.rb
121
+ - lib/smart_todo/version.rb
122
+ - lib/smart_todo_cop.rb
123
+ - shipit.rubygems.yml
124
+ - smart_todo.gemspec
125
+ homepage: https://github.com/shopify/smart_todo
126
+ licenses:
127
+ - MIT
128
+ metadata:
129
+ homepage_uri: https://github.com/shopify/smart_todo
130
+ source_code_uri: https://github.com/shopify/smart_todo
131
+ changelog_uri: https://github.com/shopify/smart_todo/blob/master/CHANGELOG.md
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.7.6
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Enhance todo's comments in your codebase.
152
+ test_files: []