Skip to content

Commit

Permalink
ver 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
datpmt committed Dec 7, 2024
1 parent 9ac960c commit a1fee68
Show file tree
Hide file tree
Showing 15 changed files with 422 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
typocop-*.gem
Empty file added CHANGELOG.md
Empty file.
6 changes: 3 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Gemfile
source 'https://rubygems.org'

gem 'octokit' # GitHub API client for Ruby
gem 'json' # JSON parsing if you're working with JSON data
gem 'rugged', '>= 0.23.0', '< 1.1.0'
gem 'cli'
gem 'octokit', '>= 9.2.0'
gem 'rugged', '1.6.3'
57 changes: 22 additions & 35 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,50 +1,37 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
faraday (1.10.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
json (2.6.2)
multipart-post (2.2.3)
octokit (4.25.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
cli (1.4.0)
faraday (2.12.1)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
json (2.9.0)
logger (1.6.2)
net-http (0.6.0)
uri
octokit (9.2.0)
faraday (>= 1, < 3)
sawyer (~> 0.9)
public_suffix (5.0.5)
ruby2_keywords (0.0.5)
rugged (1.0.1)
public_suffix (6.0.1)
rugged (1.6.3)
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
uri (1.0.2)

PLATFORMS
arm64-darwin-22
x86_64-darwin-22

DEPENDENCIES
json
octokit
rugged (>= 0.23.0, < 1.1.0)
cli
octokit (>= 9.2.0)
rugged (= 1.6.3)

BUNDLED WITH
2.3.3
2.3.5
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ runs:

- name: Install dependencies
run: |
gem install octokit json rugged:1.6.3
gem install octokit rugged:1.6.3 typocop
shell: bash

- name: Run Typocop
run: |
ruby typocop.rb
typocop execute
env:
GITHUB_TOKEN: ${{ inputs.github_token }}
PULL_REQUEST_ID: ${{ inputs.pull_request_id }}
Expand Down
3 changes: 3 additions & 0 deletions bin/typocop
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require 'typocop/cli'
Typocop::CLI.start(ARGV)
60 changes: 60 additions & 0 deletions lib/typocop.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'rugged'
# require 'pry'
require 'octokit'

require 'typocop/cli'
require 'typocop/client'
require 'typocop/comment'
require 'typocop/cop'
require 'typocop/cops'
require 'typocop/patch'
require 'typocop/repo'

GITHUB_TOKEN = ENV['GITHUB_TOKEN'] || ''
PULL_ID = ENV['PULL_REQUEST_ID']
GITHUB_BASE_REF = ENV['GITHUB_BASE_REF'] || 'main'
BASE_BRANCH = GITHUB_BASE_REF.start_with?('origin/') ? GITHUB_BASE_REF : "origin/#{GITHUB_BASE_REF}"

module Typocop
def self.execute
typo_outputs = `typos --format brief`
typo_outputs = typo_outputs.split("\n")

if typo_outputs.empty?
puts 'No typo output.'
else
result = typo_outputs.each_with_object({}) do |output, hash|
path, line, _column, typo_detail = output.split(':')
typo_match = /`(.*?)` -> `(.*?)`/.match(typo_detail)
incorrect_word, correct_word = typo_match ? typo_match.captures : []

path = path.start_with?('./') ? path[2..] : path
line = line.to_i

hash[path] ||= {}

hash[path][:typos] ||= []

existing_entry = hash[path][:typos].find { |typo| typo[:line] == line }

if existing_entry
existing_entry[:typos] << { incorrect_word: incorrect_word, correct_word: correct_word }
else
hash[path][:typos] << { line: line, typos: [{ incorrect_word: incorrect_word, correct_word: correct_word }] }
end
end

result = result.map do |path, data|
data[:typos].map do |entry|
{ path: path, line: entry[:line], typos: entry[:typos] }
end
end.flatten

cops = Cops.new(result)
cops_data = cops.cops
repo = Repo.new
client = Client.new(repo)
client.execute(cops_data)
end
end
end
11 changes: 11 additions & 0 deletions lib/typocop/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'thor'

module Typocop
class CLI < Thor
require 'typocop'
desc 'execute', 'Run typocop'
def execute
Typocop.execute
end
end
end
193 changes: 193 additions & 0 deletions lib/typocop/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
module Typocop
class Client
attr_reader :pull_comments, :repo

def initialize(repo)
@client = Octokit::Client.new(
api_endpoint: 'https://api.github.com/',
web_endpoint: 'https://github.com/',
access_token: GITHUB_TOKEN,
auto_paginate: true
)
@repo = repo
@repo_name = @repo.name

pull_comments = @client.pull_request_comments(@repo_name, PULL_ID)
@pull_comments = pull_comments.map do |comment|
Comment.new(comment.id, comment.path, comment.line, comment.body, comment.user.login)
end
end

def execute(cops)
current_cops = current_cops(cops)
if current_cops.empty?
delete_all_comments
accept_pull_request
else
dismiss_accept_pull_request
delete_comments(current_cops)
create_comments(current_cops)
end
end

private

def pull_request
@pull_request ||= @client.pull_request(@repo_name, PULL_ID)
end

def commit_id
@commit_id ||= pull_request.head.sha
end

def current_cops(cops)
result_cops = []
common_paths = common_paths(cops)
common_paths.each do |path|
patch_by_path = patch_by_path(path)
cops_by_path = cops_by_path(cops, path)
cops_lines = patch_by_path.added_lines.map(&:new_lineno) & cops_by_path.map(&:line)
next if cops_lines.empty?

result_cops.concat(cops_by_path.select { |cop| cops_lines.include?(cop.line) })
end

result_cops
end

def common_paths(cops)
@repo.patch_additions.map(&:path) & cops.map(&:path)
end

def patch_by_path(path)
@repo.patch_additions.find { |patch| patch.path == path }
end

def cops_by_path(cops, path)
cops.select { |cop| cop.path == path }
end

def create_comments(cops)
cops.each do |cop|
next if exist_comment?(cop)

line_content = line_content(cop)
suggestion_content = suggestion_content(line_content, cop.typos)

body = <<~BODY
```suggestion
#{suggestion_content}```
#{suggestion_comment(cop.typos)}
BODY

create_comment(body, cop.path, cop.line)
end
end

def suggestion_content(content, typos)
typos.each do |typo|
content.gsub!(typo[:incorrect_word], typo[:correct_word])
end
content
end

def suggestion_comment(typos)
comment = typos.map do |typo|
"`#{typo[:incorrect_word]}` with `#{typo[:correct_word]}`"
end
"Should replace #{comment.join(', ')}."
end

def line_content(cop)
patch = @repo.patch_additions.find { |patch| patch.path == cop.path }
patch.added_lines.find { |line| line.new_lineno == cop.line }.content
end

def create_comment(body, path, line)
@client.create_pull_request_comment(
@repo_name,
PULL_ID,
body,
commit_id,
path,
line,
side: 'RIGHT'
)
puts "create comment: #{path}:#{line}"
end

def exist_comment?(cop)
own_comments.any? do |comment|
comment.path == cop.path &&
comment.line == cop.line &&
!(comment.body.split('`') & cop.incorrect_words).empty?
end
end

def user_login
@user_login ||= begin
@client.user.login
rescue Octokit::Forbidden
'github-actions[bot]'
end
end

def own_comments
@own_comments ||= @pull_comments.select { |comment| comment.user_login == user_login }
end

def delete_comments(cops)
own_comments.each do |comment|
delete_comment(comment) if should_delete?(comment, cops)
end
end

def should_delete?(comment, cops)
cops.none? do |cop|
cop.path == comment.path &&
cop.line == comment.line &&
!(cop.incorrect_words & comment.body.split('`')).empty?
end
end

def delete_comment(comment)
@client.delete_pull_comment(@repo_name, comment.id)
puts "delete comment: #{comment.path}:#{comment.line}"
end

def delete_all_comments
own_comments.each do |comment|
delete_comment(comment)
end
end

def accept_pull_request
@client.create_pull_request_review(
@repo_name,
PULL_ID,
event: 'APPROVE'
)
rescue Octokit::UnprocessableEntity => e
puts e
end

def pull_request_reviews
@pull_request_reviews ||= @client.pull_request_reviews(@repo_name, PULL_ID)
end

def own_pull_request_review
@own_pull_request_review ||= pull_request_reviews.find do |review|
review.state == 'APPROVED' &&
review.user.login == user_login
end
end

def dismiss_accept_pull_request
return unless own_pull_request_review

review_id = own_pull_request_review.id
message = 'Found new typos.'
@client.dismiss_pull_request_review(@repo_name, PULL_ID, review_id, message)
end
end
end
13 changes: 13 additions & 0 deletions lib/typocop/comment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Typocop
class Comment
attr_reader :id, :path, :line, :body, :user_login

def initialize(id, path, line, body, user_login)
@id = id
@path = path
@line = line
@body = body
@user_login = user_login
end
end
end
Loading

0 comments on commit a1fee68

Please sign in to comment.