git-markdown 0.1.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 +7 -0
- data/AGENTS.md +248 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +205 -0
- data/Rakefile +48 -0
- data/bin/git-markdown +6 -0
- data/cliff.toml +53 -0
- data/git-markdown.gemspec +34 -0
- data/lefthook.yml +6 -0
- data/lib/git/markdown/api/client.rb +48 -0
- data/lib/git/markdown/api/response.rb +43 -0
- data/lib/git/markdown/cli.rb +153 -0
- data/lib/git/markdown/configuration.rb +93 -0
- data/lib/git/markdown/credentials.rb +70 -0
- data/lib/git/markdown/markdown/generator.rb +66 -0
- data/lib/git/markdown/markdown/templates/default.erb +56 -0
- data/lib/git/markdown/models/comment.rb +43 -0
- data/lib/git/markdown/models/pull_request.rb +41 -0
- data/lib/git/markdown/models/review.rb +47 -0
- data/lib/git/markdown/providers/base.rb +29 -0
- data/lib/git/markdown/providers/github.rb +74 -0
- data/lib/git/markdown/remote_parser.rb +54 -0
- data/lib/git/markdown/version.rb +5 -0
- data/lib/git_markdown.rb +34 -0
- metadata +152 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitMarkdown
|
|
4
|
+
module Api
|
|
5
|
+
class Client
|
|
6
|
+
def initialize(base_url:, token:)
|
|
7
|
+
@base_url = base_url
|
|
8
|
+
@token = token
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get(path, params = {})
|
|
12
|
+
uri = build_uri(path, params)
|
|
13
|
+
request = Net::HTTP::Get.new(uri)
|
|
14
|
+
set_headers(request)
|
|
15
|
+
|
|
16
|
+
response = http_request(uri, request)
|
|
17
|
+
Response.new(response)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def build_uri(path, params)
|
|
23
|
+
uri = URI.parse("#{@base_url}#{path}")
|
|
24
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
25
|
+
uri
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def set_headers(request)
|
|
29
|
+
request["Authorization"] = "Bearer #{@token}"
|
|
30
|
+
request["Accept"] = "application/vnd.github.v3+json"
|
|
31
|
+
request["User-Agent"] = "git-markdown/#{GitMarkdown::VERSION}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def http_request(uri, request)
|
|
35
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
36
|
+
http.use_ssl = uri.scheme == "https"
|
|
37
|
+
http.open_timeout = 10
|
|
38
|
+
http.read_timeout = 30
|
|
39
|
+
|
|
40
|
+
http.request(request)
|
|
41
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
42
|
+
raise ApiError, "Request timeout: #{e.message}"
|
|
43
|
+
rescue => e
|
|
44
|
+
raise ApiError, "Request failed: #{e.message}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitMarkdown
|
|
4
|
+
module Api
|
|
5
|
+
class Response
|
|
6
|
+
attr_reader :raw_response
|
|
7
|
+
|
|
8
|
+
def initialize(response)
|
|
9
|
+
@raw_response = response
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def success?
|
|
13
|
+
@raw_response.is_a?(Net::HTTPSuccess)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def not_found?
|
|
17
|
+
@raw_response.is_a?(Net::HTTPNotFound)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def unauthorized?
|
|
21
|
+
@raw_response.is_a?(Net::HTTPUnauthorized)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def data
|
|
25
|
+
return nil unless @raw_response.body
|
|
26
|
+
|
|
27
|
+
JSON.parse(@raw_response.body)
|
|
28
|
+
rescue JSON::ParserError
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def error_message
|
|
33
|
+
return nil if success?
|
|
34
|
+
|
|
35
|
+
if data && data["message"]
|
|
36
|
+
data["message"]
|
|
37
|
+
else
|
|
38
|
+
"HTTP #{@raw_response.code}: #{@raw_response.message}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitMarkdown
|
|
4
|
+
class CLI < Thor
|
|
5
|
+
class_option :debug, type: :boolean, default: false, desc: "Enable verbose debug output"
|
|
6
|
+
|
|
7
|
+
desc "setup", "Configure git-markdown with your GitHub token"
|
|
8
|
+
def setup
|
|
9
|
+
puts "git-markdown setup"
|
|
10
|
+
puts "=================="
|
|
11
|
+
puts
|
|
12
|
+
puts "This will store your GitHub token in ~/.config/git-markdown/credentials"
|
|
13
|
+
puts
|
|
14
|
+
puts "To create a token, visit:"
|
|
15
|
+
puts " GitHub: https://github.com/settings/personal-access-tokens"
|
|
16
|
+
puts " (For GitLab: https://gitlab.com/-/profile/personal_access_tokens)"
|
|
17
|
+
puts
|
|
18
|
+
puts "Required permissions/scopes:"
|
|
19
|
+
puts " - repo (for private repositories)"
|
|
20
|
+
puts " - read:org (for organization repositories)"
|
|
21
|
+
puts " - read:discussion (for PR discussions)"
|
|
22
|
+
puts
|
|
23
|
+
print "Enter your GitHub Personal Access Token: "
|
|
24
|
+
token = $stdin.noecho(&:gets).chomp
|
|
25
|
+
puts
|
|
26
|
+
|
|
27
|
+
if token.nil? || token.strip.empty?
|
|
28
|
+
puts "Error: Token cannot be empty"
|
|
29
|
+
exit 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
config = Configuration.new
|
|
33
|
+
config.save_credentials!(token)
|
|
34
|
+
config.save!
|
|
35
|
+
|
|
36
|
+
puts "✓ Credentials saved successfully"
|
|
37
|
+
puts "✓ Configuration saved to #{config.config_file}"
|
|
38
|
+
rescue => e
|
|
39
|
+
puts "Error: #{e.message}"
|
|
40
|
+
exit 1
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc "pr PR_IDENTIFIER", "Fetch a pull request and convert to Markdown"
|
|
44
|
+
option :output, type: :string, desc: "Output directory or file path"
|
|
45
|
+
option :stdout, type: :boolean, default: false, desc: "Output to stdout instead of file", lazy_default: true
|
|
46
|
+
option :status, type: :string, default: "unresolved", enum: %w[unresolved resolved all],
|
|
47
|
+
desc: "Filter comments by status"
|
|
48
|
+
def pr(identifier)
|
|
49
|
+
config = Configuration.load
|
|
50
|
+
owner, repo, number = parse_identifier(identifier)
|
|
51
|
+
|
|
52
|
+
debug_log(options[:debug], "Fetching PR #{owner}/#{repo}##{number}")
|
|
53
|
+
debug_log(options[:debug], "API URL: #{config.api_url}")
|
|
54
|
+
|
|
55
|
+
provider = create_provider(config)
|
|
56
|
+
|
|
57
|
+
puts "Fetching pull request..." unless options[:stdout]
|
|
58
|
+
pull_request = provider.fetch_pull_request(owner, repo, number)
|
|
59
|
+
debug_log(options[:debug], "PR title: #{pull_request.title}")
|
|
60
|
+
|
|
61
|
+
puts "Fetching comments..." unless options[:stdout]
|
|
62
|
+
comments = provider.fetch_comments(owner, repo, number)
|
|
63
|
+
debug_log(options[:debug], "Found #{comments.length} general comments")
|
|
64
|
+
|
|
65
|
+
puts "Fetching reviews..." unless options[:stdout]
|
|
66
|
+
reviews = provider.fetch_reviews(owner, repo, number)
|
|
67
|
+
debug_log(options[:debug], "Found #{reviews.length} reviews")
|
|
68
|
+
|
|
69
|
+
generator = Markdown::Generator.new(
|
|
70
|
+
pull_request,
|
|
71
|
+
comments,
|
|
72
|
+
reviews,
|
|
73
|
+
status_filter: options[:status].to_sym
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
markdown = generator.generate
|
|
77
|
+
|
|
78
|
+
if options[:stdout]
|
|
79
|
+
puts markdown
|
|
80
|
+
else
|
|
81
|
+
output_path = determine_output_path(options[:output], generator.filename)
|
|
82
|
+
File.write(output_path, markdown)
|
|
83
|
+
puts "✓ Saved to #{output_path}"
|
|
84
|
+
end
|
|
85
|
+
rescue AuthenticationError => e
|
|
86
|
+
puts "Authentication error: #{e.message}"
|
|
87
|
+
exit 1
|
|
88
|
+
rescue NotFoundError => e
|
|
89
|
+
puts "Not found: #{e.message}"
|
|
90
|
+
exit 1
|
|
91
|
+
rescue ApiError => e
|
|
92
|
+
puts "API error: #{e.message}"
|
|
93
|
+
debug_log(options[:debug], e.backtrace.join("\n"))
|
|
94
|
+
exit 1
|
|
95
|
+
rescue => e
|
|
96
|
+
puts "Error: #{e.message}"
|
|
97
|
+
debug_log(options[:debug], e.backtrace.join("\n"))
|
|
98
|
+
exit 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
desc "version", "Show version"
|
|
102
|
+
def version
|
|
103
|
+
puts GitMarkdown::VERSION
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
default_task :help
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def parse_identifier(identifier)
|
|
111
|
+
if identifier.include?("#")
|
|
112
|
+
parts = identifier.split("#")
|
|
113
|
+
repo_parts = parts[0].split("/")
|
|
114
|
+
raise Error, "Invalid format. Use: owner/repo#123 or just 123" unless repo_parts.length == 2
|
|
115
|
+
|
|
116
|
+
[repo_parts[0], repo_parts[1], parts[1].to_i]
|
|
117
|
+
|
|
118
|
+
else
|
|
119
|
+
remote = RemoteParser.from_git_remote
|
|
120
|
+
if remote.nil? || !remote.valid?
|
|
121
|
+
raise Error, "Cannot detect repository from git remote. Use owner/repo#123 format."
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
[remote.owner, remote.repo, identifier.to_i]
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def create_provider(config)
|
|
129
|
+
case config.provider
|
|
130
|
+
when :github
|
|
131
|
+
Providers::GitHub.new(config)
|
|
132
|
+
else
|
|
133
|
+
raise Error, "Unsupported provider: #{config.provider}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def determine_output_path(output_option, filename)
|
|
138
|
+
if output_option.nil?
|
|
139
|
+
File.join(Dir.pwd, filename)
|
|
140
|
+
elsif File.directory?(output_option)
|
|
141
|
+
File.join(output_option, filename)
|
|
142
|
+
else
|
|
143
|
+
output_option
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def debug_log(debug, message)
|
|
148
|
+
return unless debug
|
|
149
|
+
|
|
150
|
+
warn "[DEBUG] #{message}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module GitMarkdown
|
|
6
|
+
class Configuration
|
|
7
|
+
XDG_CONFIG_HOME = ENV.fetch("XDG_CONFIG_HOME", File.expand_path("~/.config"))
|
|
8
|
+
DEFAULT_PROVIDER = :github
|
|
9
|
+
|
|
10
|
+
attr_accessor :token, :provider, :api_url, :output_dir, :default_status
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@provider = DEFAULT_PROVIDER
|
|
14
|
+
@output_dir = Dir.pwd
|
|
15
|
+
@default_status = :unresolved
|
|
16
|
+
@api_url = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.load
|
|
20
|
+
new.load!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def load!
|
|
24
|
+
load_from_file if config_file_exist?
|
|
25
|
+
resolve_credentials
|
|
26
|
+
resolve_api_url if api_url.nil?
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def config_dir
|
|
31
|
+
File.join(XDG_CONFIG_HOME, "git-markdown")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def config_file
|
|
35
|
+
File.join(config_dir, "config.yml")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def credentials_file
|
|
39
|
+
File.join(config_dir, "credentials")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def save!
|
|
43
|
+
FileUtils.mkdir_p(config_dir)
|
|
44
|
+
File.write(config_file, config_to_yaml)
|
|
45
|
+
FileUtils.chmod(0o700, config_dir)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def save_credentials!(token_value)
|
|
49
|
+
FileUtils.mkdir_p(config_dir)
|
|
50
|
+
File.write(credentials_file, token_value)
|
|
51
|
+
FileUtils.chmod(0o600, credentials_file)
|
|
52
|
+
@token = token_value
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def config_file_exist?
|
|
58
|
+
File.exist?(config_file)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def load_from_file
|
|
62
|
+
require "yaml"
|
|
63
|
+
config = YAML.safe_load_file(
|
|
64
|
+
config_file,
|
|
65
|
+
symbolize_names: true,
|
|
66
|
+
permitted_classes: [Symbol]
|
|
67
|
+
)
|
|
68
|
+
@provider = config[:provider] if config[:provider]
|
|
69
|
+
@api_url = config[:api_url] if config[:api_url]
|
|
70
|
+
@output_dir = config[:output_dir] if config[:output_dir]
|
|
71
|
+
@default_status = config[:default_status].to_sym if config[:default_status]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def resolve_credentials
|
|
75
|
+
@token ||= Credentials.resolve(debug: false)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def resolve_api_url
|
|
79
|
+
@api_url = ENV.fetch("GITHUB_API_URL") do
|
|
80
|
+
(@provider == :github) ? "https://api.github.com" : nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def config_to_yaml
|
|
85
|
+
{
|
|
86
|
+
provider: @provider,
|
|
87
|
+
api_url: @api_url,
|
|
88
|
+
output_dir: @output_dir,
|
|
89
|
+
default_status: @default_status
|
|
90
|
+
}.to_yaml
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitMarkdown
|
|
4
|
+
class Credentials
|
|
5
|
+
class << self
|
|
6
|
+
def resolve(debug: false)
|
|
7
|
+
token = from_env || from_git_credential || from_gh_cli || from_file
|
|
8
|
+
|
|
9
|
+
raise AuthenticationError, "No GitHub token found. Run `git-markdown setup` to configure." if token.nil?
|
|
10
|
+
|
|
11
|
+
token.strip
|
|
12
|
+
rescue => e
|
|
13
|
+
raise AuthenticationError, "Failed to resolve credentials: #{e.message}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def from_env
|
|
17
|
+
ENV["GITHUB_TOKEN"] || ENV["GH_TOKEN"]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def from_git_credential
|
|
21
|
+
host = "github.com"
|
|
22
|
+
protocol = "https"
|
|
23
|
+
|
|
24
|
+
input = "protocol=#{protocol}\nhost=#{host}\n"
|
|
25
|
+
output = nil
|
|
26
|
+
env = {
|
|
27
|
+
"GIT_TERMINAL_PROMPT" => "0",
|
|
28
|
+
"GIT_ASKPASS" => "/bin/false",
|
|
29
|
+
"SSH_ASKPASS" => "/bin/false"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
IO.popen(env, "git credential fill 2>/dev/null", "r+") do |io|
|
|
33
|
+
io.write(input)
|
|
34
|
+
io.close_write
|
|
35
|
+
output = io.read
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
return nil unless $?.success?
|
|
39
|
+
|
|
40
|
+
output.each_line do |line|
|
|
41
|
+
key, value = line.chomp.split("=", 2)
|
|
42
|
+
return value if key == "password"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
nil
|
|
46
|
+
rescue
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def from_gh_cli
|
|
51
|
+
output = `gh auth token 2>/dev/null`
|
|
52
|
+
output.strip if $?.success? && !output.strip.empty?
|
|
53
|
+
rescue
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def from_file
|
|
58
|
+
credentials_file = File.join(
|
|
59
|
+
Configuration::XDG_CONFIG_HOME,
|
|
60
|
+
"git-markdown",
|
|
61
|
+
"credentials"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return nil unless File.exist?(credentials_file)
|
|
65
|
+
|
|
66
|
+
File.read(credentials_file).strip
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitMarkdown
|
|
4
|
+
module Markdown
|
|
5
|
+
class Generator
|
|
6
|
+
def initialize(pull_request, comments, reviews, status_filter: :unresolved)
|
|
7
|
+
@pr = pull_request
|
|
8
|
+
@comments = comments
|
|
9
|
+
@reviews = reviews
|
|
10
|
+
@status_filter = status_filter
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def generate
|
|
14
|
+
template = File.read(template_path)
|
|
15
|
+
erb = ERB.new(template, trim_mode: "-")
|
|
16
|
+
erb.result(binding)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def filename
|
|
20
|
+
title_slug = @pr.title.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/^-|-$/, "")
|
|
21
|
+
"PR-#{@pr.number}-#{title_slug}.md"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def template_path
|
|
27
|
+
File.join(__dir__, "templates", "default.erb")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def format_date(date_string)
|
|
31
|
+
return nil unless date_string
|
|
32
|
+
|
|
33
|
+
Time.parse(date_string).strftime("%Y-%m-%d %H:%M UTC")
|
|
34
|
+
rescue
|
|
35
|
+
date_string
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def filtered_inline_comments
|
|
39
|
+
@reviews.flat_map(&:comments).select do |comment|
|
|
40
|
+
include_comment?(comment)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def filtered_general_comments
|
|
45
|
+
@comments.select do |comment|
|
|
46
|
+
include_comment?(comment)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def include_comment?(comment)
|
|
51
|
+
case @status_filter
|
|
52
|
+
when :unresolved
|
|
53
|
+
!comment.body.include?("[resolved]") && !comment.body.include?("[done]")
|
|
54
|
+
when :resolved
|
|
55
|
+
comment.body.include?("[resolved]") || comment.body.include?("[done]")
|
|
56
|
+
else
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def group_comments_by_file(comments)
|
|
62
|
+
comments.select(&:inline?).group_by(&:path)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# <%= @pr.title %>
|
|
2
|
+
|
|
3
|
+
**#<%= @pr.number %>** by @<%= @pr.author %> | <%= @pr.state.upcase %> | <%= format_date(@pr.created_at) %>
|
|
4
|
+
|
|
5
|
+
[View on GitHub](<%= @pr.html_url %>)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<%= @pr.body %>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<% inline_comments = filtered_inline_comments %>
|
|
14
|
+
<% if inline_comments.any? %>
|
|
15
|
+
## Review Comments
|
|
16
|
+
|
|
17
|
+
<% group_comments_by_file(inline_comments).each do |file, comments| %>
|
|
18
|
+
### <%= file %>
|
|
19
|
+
|
|
20
|
+
<% comments.sort_by(&:line).each do |comment| %>
|
|
21
|
+
**Line <%= comment.line %>** — @<%= comment.author %> (<%= format_date(comment.created_at) %>)
|
|
22
|
+
|
|
23
|
+
<%= comment.body %>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<% end %>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
30
|
+
|
|
31
|
+
<% general_comments = filtered_general_comments %>
|
|
32
|
+
<% if general_comments.any? %>
|
|
33
|
+
## General Comments
|
|
34
|
+
|
|
35
|
+
<% general_comments.each do |comment| %>
|
|
36
|
+
### @<%= comment.author %> (<%= format_date(comment.created_at) %>)
|
|
37
|
+
|
|
38
|
+
<%= comment.body %>
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
<% end %>
|
|
43
|
+
<% end %>
|
|
44
|
+
|
|
45
|
+
<% if @reviews.any? { |r| r.body && !r.body.empty? } %>
|
|
46
|
+
## Review Summaries
|
|
47
|
+
|
|
48
|
+
<% @reviews.select { |r| r.body && !r.body.empty? }.each do |review| %>
|
|
49
|
+
### @<%= review.author %> — <%= review.state %>
|
|
50
|
+
|
|
51
|
+
<%= review.body %>
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
<% end %>
|
|
56
|
+
<% end %>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitMarkdown
|
|
4
|
+
module Models
|
|
5
|
+
class Comment
|
|
6
|
+
attr_reader :id, :body, :author, :path, :line, :html_url, :created_at, :updated_at, :in_reply_to_id
|
|
7
|
+
|
|
8
|
+
def initialize(attrs = {})
|
|
9
|
+
@id = attrs[:id]
|
|
10
|
+
@body = attrs[:body] || ""
|
|
11
|
+
@author = attrs[:author]
|
|
12
|
+
@path = attrs[:path]
|
|
13
|
+
@line = attrs[:line]
|
|
14
|
+
@html_url = attrs[:html_url]
|
|
15
|
+
@created_at = attrs[:created_at]
|
|
16
|
+
@updated_at = attrs[:updated_at]
|
|
17
|
+
@in_reply_to_id = attrs[:in_reply_to_id]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.from_api(data)
|
|
21
|
+
new(
|
|
22
|
+
id: data["id"],
|
|
23
|
+
body: data["body"],
|
|
24
|
+
author: data.dig("user", "login"),
|
|
25
|
+
path: data["path"],
|
|
26
|
+
line: data["line"] || data["original_line"],
|
|
27
|
+
html_url: data["html_url"],
|
|
28
|
+
created_at: data["created_at"],
|
|
29
|
+
updated_at: data["updated_at"],
|
|
30
|
+
in_reply_to_id: data["in_reply_to_id"]
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def inline?
|
|
35
|
+
!@path.nil?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reply?
|
|
39
|
+
!@in_reply_to_id.nil?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitMarkdown
|
|
4
|
+
module Models
|
|
5
|
+
class PullRequest
|
|
6
|
+
attr_reader :number, :title, :body, :state, :author, :html_url, :created_at, :updated_at
|
|
7
|
+
|
|
8
|
+
def initialize(attrs = {})
|
|
9
|
+
@number = attrs[:number]
|
|
10
|
+
@title = attrs[:title]
|
|
11
|
+
@body = attrs[:body] || ""
|
|
12
|
+
@state = attrs[:state]
|
|
13
|
+
@author = attrs[:author]
|
|
14
|
+
@html_url = attrs[:html_url]
|
|
15
|
+
@created_at = attrs[:created_at]
|
|
16
|
+
@updated_at = attrs[:updated_at]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.from_api(data)
|
|
20
|
+
new(
|
|
21
|
+
number: data["number"],
|
|
22
|
+
title: data["title"],
|
|
23
|
+
body: data["body"],
|
|
24
|
+
state: data["state"],
|
|
25
|
+
author: data.dig("user", "login"),
|
|
26
|
+
html_url: data["html_url"],
|
|
27
|
+
created_at: data["created_at"],
|
|
28
|
+
updated_at: data["updated_at"]
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def open?
|
|
33
|
+
@state == "open"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def closed?
|
|
37
|
+
@state == "closed"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitMarkdown
|
|
4
|
+
module Models
|
|
5
|
+
class Review
|
|
6
|
+
attr_reader :id, :state, :body, :author, :html_url, :submitted_at
|
|
7
|
+
attr_accessor :comments
|
|
8
|
+
|
|
9
|
+
def initialize(attrs = {})
|
|
10
|
+
@id = attrs[:id]
|
|
11
|
+
@state = attrs[:state]
|
|
12
|
+
@body = attrs[:body] || ""
|
|
13
|
+
@author = attrs[:author]
|
|
14
|
+
@html_url = attrs[:html_url]
|
|
15
|
+
@submitted_at = attrs[:submitted_at]
|
|
16
|
+
@comments = attrs[:comments] || []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.from_api(data)
|
|
20
|
+
new(
|
|
21
|
+
id: data["id"],
|
|
22
|
+
state: data["state"],
|
|
23
|
+
body: data["body"],
|
|
24
|
+
author: data.dig("user", "login"),
|
|
25
|
+
html_url: data["html_url"],
|
|
26
|
+
submitted_at: data["submitted_at"]
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def approved?
|
|
31
|
+
@state == "APPROVED"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def changes_requested?
|
|
35
|
+
@state == "CHANGES_REQUESTED"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def commented?
|
|
39
|
+
@state == "COMMENTED"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def dismissed?
|
|
43
|
+
@state == "DISMISSED"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitMarkdown
|
|
4
|
+
module Providers
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :config
|
|
7
|
+
|
|
8
|
+
def initialize(config)
|
|
9
|
+
@config = config
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def fetch_pull_request(owner, repo, number)
|
|
13
|
+
raise NotImplementedError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def fetch_comments(owner, repo, number)
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def fetch_reviews(owner, repo, number)
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def fetch_review_comments(owner, repo, review_id)
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|