Skip to content

Commit 9bfbc67

Browse files
committed
refactor(github-ingestor): rubocop lint ok
1 parent ceff245 commit 9bfbc67

5 files changed

Lines changed: 297 additions & 213 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# frozen_string_literal: true
2+
3+
module Ingestors
4+
module Concerns
5+
# All methods for the Github Ingestor Class
6+
# This involves, the caching, the change from github.com to api.github.com
7+
module GithubIngestorMaterialHelpers
8+
private
9+
10+
def resolve_url(repo_data)
11+
homepage_nil_or_empty = repo_data['homepage'].nil? || repo_data['homepage'].empty?
12+
url = homepage_nil_or_empty ? repo_data['html_url'] : get_redirected_url(repo_data['homepage'])
13+
[url, homepage_nil_or_empty]
14+
end
15+
16+
def fetch_homepage_doc(url)
17+
response = HTTParty.get(url, follow_redirects: true, headers: { 'User-Agent' => config[:user_agent] })
18+
Nokogiri::HTML(response.body)
19+
end
20+
21+
# DEFINITION – Opens the GitHub homepage, fetches the 3 first >25 char <p> tags'text
22+
# and joins them with a 'Read more...' link at the end of the description
23+
# Some of the first <p> tags were not descriptive, thus skipping them
24+
def fetch_definition(doc, url)
25+
desc = ''
26+
round = 3
27+
doc.css('p').each do |p|
28+
p_txt = p&.text&.strip&.gsub(/\s+/, ' ')
29+
next if (p_txt.length < 25) || round.zero?
30+
31+
desc = "#{desc}\n#{p_txt}"
32+
round -= 1
33+
end
34+
"#{desc}\n(...) [Read more...](#{url})"
35+
end
36+
37+
# LICENCE – Get proper licence
38+
# the licence must match the format of config/dictionaries/licences.yml
39+
def fetch_licence(licence)
40+
return 'notspecified' if licence.nil? || licence == 'null'
41+
return 'other-at' if licence['key'] == 'other'
42+
43+
licence['spdx_id']
44+
end
45+
46+
# DOI – Fetches DOI from various sources in a repo
47+
# I chose to only read the `README.md` as it seems to have the DOI badge almost everytime.
48+
# Whereas enabling the fetching of CITATION.cff or CITATION.md would result in increasing
49+
# the number of api request.
50+
def fetch_doi(full_name)
51+
fetch_doi_from_file(full_name, 'README.md')
52+
53+
# doi = fetch_doi_from_file(full_name, 'CITATION.cff')
54+
# return doi if doi
55+
56+
# doi = fetch_doi_from_file(full_name, 'CITATION.md')
57+
# return doi if doi
58+
rescue StandardError
59+
nil
60+
end
61+
62+
# DOI – Fetches DOI from a specific file in repo (via GitHub API)
63+
def fetch_doi_from_file(full_name, filename)
64+
url = "#{GITHUB_API_BASE}/#{full_name}/contents/#{filename}"
65+
data = get_or_set_cache("doi_#{full_name.gsub('/', '_')}_#{filename.downcase}", url)
66+
return nil unless data && data['content']
67+
68+
decoded = Base64.decode64(data['content'])
69+
doi_match = decoded.match(%r{doi.org/\s*([^\s,)]+)}i)
70+
doi_match ? "https://doi.org/#{doi_match[1]}" : nil
71+
rescue StandardError
72+
nil
73+
end
74+
75+
# RELEASE – Opens releases API address and returns last release
76+
def fetch_latest_release(full_name)
77+
url = "#{GITHUB_API_BASE}/#{full_name}/releases"
78+
releases = get_or_set_cache("releases_#{full_name.gsub('/', '_')}", url)
79+
releases.is_a?(Array) && releases.first ? releases.first['tag_name'] : nil
80+
rescue StandardError
81+
nil
82+
end
83+
84+
# CONTRIBUTORS – Opens contributors API address and returns list of contributors
85+
def fetch_contributors(contributors_url)
86+
contributors = get_or_set_cache("contributors_#{full_name.gsub('/', '_')}", contributors_url)
87+
contributors.map { |c| (c['login']) }
88+
rescue StandardError
89+
nil
90+
end
91+
92+
# PREREQUISITES – From the homepage HTML, looks for <p> tags which are children of ...
93+
def fetch_prerequisites(doc)
94+
prereq_paragraphs = []
95+
96+
# ... any heading tag (h1–h6) or span tag with text "prereq" (EN) or "prerreq" (ES)
97+
prereq_paragraphs = fetch_prerequisites_from_h(doc, prereq_paragraphs)
98+
99+
# ... any tag with id containing "prereq" (EN) or "prerreq" (ES)
100+
prereq_paragraphs = fetch_prerequisites_from_id_or_class(doc, prereq_paragraphs) if prereq_paragraphs.empty?
101+
102+
prereq_paragraphs&.join("\n")&.gsub(/\n\n+/, "\n")&.to_s&.strip
103+
end
104+
105+
def fetch_prerequisites_from_h(doc, prereq_paragraphs)
106+
doc.xpath('//h1|//h2|//h3|//h4|//h5|//h6|//span').each do |h|
107+
next unless h.text =~ /prereq|prerreq/i # if prereq in text
108+
109+
paragraph = h.xpath('following-sibling::*')
110+
.take_while { |sib| %w[p ul ol].include?(sib.name) } # take either p, ul or ol
111+
prereq_paragraphs.concat(paragraph) if paragraph
112+
end
113+
prereq_paragraphs
114+
end
115+
116+
def fetch_prerequisites_from_id_or_class(doc, prereq_paragraphs)
117+
doc.xpath('//*[@id]').each do |node|
118+
next unless prereq_node?(node)
119+
120+
extract_following_paragraphs(node, prereq_paragraphs)
121+
extract_nested_paragraphs(node, prereq_paragraphs) if prereq_paragraphs.empty?
122+
end
123+
prereq_paragraphs
124+
end
125+
126+
def prereq_node?(node)
127+
[node['id'], node['class']].compact.any? { |attr| attr =~ /prereq|prerreq/i }
128+
end
129+
130+
def extract_following_paragraphs(node, prereq_paragraphs)
131+
paragraphs = node.xpath('following-sibling::*')
132+
.take_while { |sib| %w[p ul ol].include?(sib.name) }
133+
prereq_paragraphs.concat(paragraphs) if paragraphs
134+
end
135+
136+
def extract_nested_paragraphs(node, prereq_paragraphs)
137+
paragraphs = node.xpath('.//p | .//ul | .//ol')
138+
prereq_paragraphs.concat(paragraphs) if paragraphs.any?
139+
end
140+
end
141+
end
142+
end
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
module Ingestors
4+
module Concerns
5+
# This module is to change the github.{com|io} to api.github.com
6+
module GithubIngestorReadHelpers
7+
private
8+
9+
# Takes a github.{com|io} url and returns its api.google.com url
10+
def to_github_api(url)
11+
uri = URI(url)
12+
return nil unless uri.host =~ /github\.com|github\.io/i
13+
14+
if uri.host.end_with?('github.io')
15+
github_api_from_io(uri)
16+
elsif uri.host.end_with?('github.com')
17+
github_api_from_com(uri)
18+
end
19+
end
20+
21+
def github_host?(host)
22+
host =~ /github\.com|github\.io/i
23+
end
24+
25+
def github_api_from_io(uri)
26+
parts = uri.path.split('/')
27+
repo = parts[1]
28+
owner = uri.host.split('.').first
29+
"#{GITHUB_API_BASE}/#{owner}/#{repo}"
30+
end
31+
32+
def github_api_from_com(uri)
33+
parts = uri.path.split('/')
34+
"#{GITHUB_API_BASE}/#{parts[1]}/#{parts[2]}"
35+
end
36+
end
37+
end
38+
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
module Ingestors
4+
module Concerns
5+
# From a sitemap.{xml|txt} or a single URL, get the list of URLs (= sources)
6+
module SitemapHelpers
7+
private
8+
9+
def get_sources(source_url)
10+
case source_url.downcase
11+
when /sitemap(.*)?\.xml\Z/
12+
parse_xml_sitemap(source_url)
13+
when /sitemap(.*)?\.txt\Z/
14+
parse_txt_sitemap(source_url)
15+
else
16+
[source_url]
17+
end
18+
end
19+
20+
def parse_xml_sitemap(url)
21+
urls = SitemapParser.new(
22+
url,
23+
recurse: true,
24+
url_regex: /(github\.com|github\.io)/i,
25+
headers: { 'User-Agent' => config[:user_agent] }
26+
).to_a.uniq.map(&:strip)
27+
28+
log_sitemap('xml', url, urls.count)
29+
urls
30+
end
31+
32+
def parse_txt_sitemap(url)
33+
urls = open_url(url).to_a.uniq.map(&:strip)
34+
35+
log_sitemap('txt', url, urls.count)
36+
urls
37+
end
38+
39+
def log_sitemap(ext, url, count)
40+
@messages << "Parsing .#{ext} sitemap: #{url}\n - #{count} URLs found"
41+
end
42+
end
43+
end
44+
end

0 commit comments

Comments
 (0)