fbe 0.48.4 → 0.48.6
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 +4 -4
- data/.github/workflows/actionlint.yml +1 -7
- data/.github/workflows/codecov.yml +2 -2
- data/.github/workflows/markdown-lint.yml +1 -1
- data/.github/workflows/rake.yml +1 -2
- data/.github/workflows/typos.yml +1 -1
- data/.gitignore +2 -0
- data/.rubocop.yml +18 -2
- data/.ruby-version +1 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +32 -32
- data/README.md +2 -2
- data/REUSE.toml +1 -0
- data/assets/bylaws/bad-branch-name-was-punished.fe.liquid +1 -1
- data/fbe.gemspec +1 -1
- data/lib/fbe/award.rb +1 -1
- data/lib/fbe/conclude.rb +16 -9
- data/lib/fbe/consider.rb +5 -1
- data/lib/fbe/delete.rb +7 -1
- data/lib/fbe/delete_one.rb +11 -2
- data/lib/fbe/fake_octokit.rb +1797 -0
- data/lib/fbe/fb.rb +31 -28
- data/lib/fbe/github_graph.rb +28 -19
- data/lib/fbe/if_absent.rb +1 -1
- data/lib/fbe/iterate.rb +42 -24
- data/lib/fbe/just_one.rb +5 -2
- data/lib/fbe/kill_if.rb +1 -0
- data/lib/fbe/middleware/formatter.rb +25 -11
- data/lib/fbe/middleware/rate_limit.rb +96 -48
- data/lib/fbe/middleware/sqlite_store.rb +12 -4
- data/lib/fbe/octo.rb +187 -1944
- data/lib/fbe/over.rb +7 -3
- data/lib/fbe/overwrite.rb +31 -2
- data/lib/fbe/pmp.rb +56 -34
- data/lib/fbe/regularly.rb +3 -3
- data/lib/fbe/repeatedly.rb +2 -2
- data/lib/fbe/sec.rb +4 -5
- data/lib/fbe/tombstone.rb +1 -1
- data/lib/fbe/unmask_repos.rb +1 -2
- data/lib/fbe.rb +1 -1
- metadata +4 -2
data/lib/fbe/over.rb
CHANGED
|
@@ -22,9 +22,13 @@ def Fbe.over?(
|
|
|
22
22
|
epoch: $epoch || Time.now, kickoff: $kickoff || Time.now,
|
|
23
23
|
quota_aware: true, lifetime_aware: true, timeout_aware: true
|
|
24
24
|
)
|
|
25
|
-
if quota_aware
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
if quota_aware
|
|
26
|
+
octo = Fbe.octo(loog:, options:, global:)
|
|
27
|
+
if octo.off_quota?(threshold: 100, resource: :core) ||
|
|
28
|
+
octo.off_quota?(resource: :search, threshold: 10)
|
|
29
|
+
loog.info('We are off GitHub quota, time to stop')
|
|
30
|
+
return true
|
|
31
|
+
end
|
|
28
32
|
end
|
|
29
33
|
if lifetime_aware && options.lifetime && Time.now - epoch > options.lifetime * 0.9
|
|
30
34
|
loog.info("We ran out of lifetime (#{epoch.ago} already), must stop here")
|
data/lib/fbe/overwrite.rb
CHANGED
|
@@ -40,19 +40,41 @@ def Fbe.overwrite(fact, property_or_hash, values = nil, fb: Fbe.fb, fid: '_id')
|
|
|
40
40
|
before[k.to_s] = fact[k]
|
|
41
41
|
end
|
|
42
42
|
modified = false
|
|
43
|
+
overwrites = false
|
|
43
44
|
property_or_hash.each do |k, vv|
|
|
45
|
+
sk = k.to_s
|
|
44
46
|
raise(Fbe::Error, "The value for #{k} is nil") if vv.nil?
|
|
45
47
|
vv = [vv] unless vv.is_a?(Array)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
existing = before[sk]
|
|
49
|
+
next if existing == vv
|
|
50
|
+
before[sk] = vv
|
|
48
51
|
modified = true
|
|
52
|
+
overwrites = true unless existing.nil?
|
|
49
53
|
end
|
|
50
54
|
return fact unless modified
|
|
55
|
+
unless overwrites
|
|
56
|
+
property_or_hash.each do |k, vv|
|
|
57
|
+
sk = k.to_s
|
|
58
|
+
next unless fact[sk].nil?
|
|
59
|
+
vv = [vv] unless vv.is_a?(Array)
|
|
60
|
+
vv.each do |v|
|
|
61
|
+
fact.public_send(:"#{sk}=", v)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
return
|
|
65
|
+
end
|
|
51
66
|
id = fact[fid]&.first
|
|
52
67
|
raise(Fbe::Error, "There is no #{fid} in the fact, cannot use Fbe.overwrite") if id.nil?
|
|
53
68
|
raise(Fbe::Error, "No facts by #{fid} = #{id}") if fb.query("(eq #{fid} #{id})").delete!.zero?
|
|
54
69
|
fb.txn do |fbt|
|
|
55
70
|
n = fbt.insert
|
|
71
|
+
f = n
|
|
72
|
+
while f.instance_variable_defined?(:@fact) || f.instance_variable_defined?(:@origin)
|
|
73
|
+
iv = f.instance_variable_defined?(:@fact) ? :@fact : :@origin
|
|
74
|
+
f = f.instance_variable_get(iv)
|
|
75
|
+
end
|
|
76
|
+
map = f.instance_variable_get(:@map)
|
|
77
|
+
%w[_id _time _version _job].each { |k| map.delete(k) if before.key?(k) }
|
|
56
78
|
before.each do |k, vv|
|
|
57
79
|
next unless n[k].nil?
|
|
58
80
|
vv.each do |v|
|
|
@@ -82,6 +104,13 @@ def Fbe.overwrite(fact, property_or_hash, values = nil, fb: Fbe.fb, fid: '_id')
|
|
|
82
104
|
raise(Fbe::Error, "No facts by #{fid} = #{id}") if fb.query("(eq #{fid} #{id})").delete!.zero?
|
|
83
105
|
fb.txn do |fbt|
|
|
84
106
|
n = fbt.insert
|
|
107
|
+
f = n
|
|
108
|
+
while f.instance_variable_defined?(:@fact) || f.instance_variable_defined?(:@origin)
|
|
109
|
+
iv = f.instance_variable_defined?(:@fact) ? :@fact : :@origin
|
|
110
|
+
f = f.instance_variable_get(iv)
|
|
111
|
+
end
|
|
112
|
+
map = f.instance_variable_get(:@map)
|
|
113
|
+
%w[_id _time _version _job].each { |k| map.delete(k) if before.key?(k) }
|
|
85
114
|
before[property.to_s] = values
|
|
86
115
|
before.each do |k, vv|
|
|
87
116
|
next unless n[k].nil?
|
data/lib/fbe/pmp.rb
CHANGED
|
@@ -28,6 +28,13 @@ require_relative 'fb'
|
|
|
28
28
|
# Fbe.pmp.cost.hourly_rate
|
|
29
29
|
# Fbe.pmp.time.deadline
|
|
30
30
|
#
|
|
31
|
+
# Custom areas (not defined in +assets/pmp.xml+) are also supported. When
|
|
32
|
+
# accessing a property through a custom area, the returned value is read
|
|
33
|
+
# directly from the factbase without XML defaults or type coercion. The
|
|
34
|
+
# returned +Pmpv+ object will have +nil+ for +default+, +type+, and +memo+.
|
|
35
|
+
#
|
|
36
|
+
# Fbe.pmp.my_custom.my_prop # reads from factbase, no XML defaults
|
|
37
|
+
#
|
|
31
38
|
# @param [Factbase] fb The factbase
|
|
32
39
|
# @param [Hash] global The hash for global caching
|
|
33
40
|
# @param [Judges::Options] options The options coming from the +judges+ tool
|
|
@@ -42,7 +49,10 @@ require_relative 'fb'
|
|
|
42
49
|
#
|
|
43
50
|
# # Get deadline from time area
|
|
44
51
|
# deadline = Fbe.pmp.time.deadline
|
|
45
|
-
|
|
52
|
+
#
|
|
53
|
+
# # Read custom property (nil default/type/memo)
|
|
54
|
+
# val = Fbe.pmp.my_custom.my_prop
|
|
55
|
+
def Fbe.pmp(fb: Fbe.fb, global: $global, options: $options, loog: $loog) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
46
56
|
xml = Nokogiri::XML(File.read(File.join(__dir__, '../../assets/pmp.xml')))
|
|
47
57
|
pmpv =
|
|
48
58
|
Class.new(SimpleDelegator) do
|
|
@@ -55,6 +65,7 @@ def Fbe.pmp(fb: Fbe.fb, global: $global, options: $options, loog: $loog) # ruboc
|
|
|
55
65
|
@memo = memo
|
|
56
66
|
end
|
|
57
67
|
end
|
|
68
|
+
query = ->(area) { Fbe.fb(global:, fb:, options:, loog:).query("(and (eq what 'pmp') (eq area '#{area}'))") }
|
|
58
69
|
Class.new do
|
|
59
70
|
define_method(:areas) do
|
|
60
71
|
xml.xpath('/pmp/area/@name').map(&:value)
|
|
@@ -62,42 +73,53 @@ def Fbe.pmp(fb: Fbe.fb, global: $global, options: $options, loog: $loog) # ruboc
|
|
|
62
73
|
others do |*args1|
|
|
63
74
|
area = args1.first.to_s
|
|
64
75
|
node = xml.at_xpath("/pmp/area[@name='#{area}']")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
if node.nil?
|
|
77
|
+
Class.new do
|
|
78
|
+
define_method(:properties) do
|
|
79
|
+
query.call(area).each.first&.all_properties&.map(&:to_s) || []
|
|
80
|
+
end
|
|
81
|
+
others do |*args2|
|
|
82
|
+
param = args2.first.to_s
|
|
83
|
+
result = query.call(area).each.first&.[](param)&.first
|
|
84
|
+
pmpv.new(result, nil, nil, nil)
|
|
85
|
+
end
|
|
86
|
+
end.new
|
|
87
|
+
else
|
|
88
|
+
Class.new do
|
|
89
|
+
define_method(:properties) do
|
|
90
|
+
node.xpath('p/name').map(&:text)
|
|
91
|
+
end
|
|
92
|
+
others do |*args2|
|
|
93
|
+
param = args2.first.to_s
|
|
94
|
+
result = query.call(area).each.first&.[](param)&.first
|
|
95
|
+
prop = node.at_xpath("p[name='#{param}']")
|
|
96
|
+
default = nil
|
|
97
|
+
type = nil
|
|
98
|
+
memo = nil
|
|
99
|
+
if prop
|
|
100
|
+
default = prop.at_xpath('default').text
|
|
101
|
+
type = prop.at_xpath('type').text
|
|
102
|
+
memo = prop.at_xpath('memo').text
|
|
103
|
+
default =
|
|
104
|
+
case type
|
|
105
|
+
when 'int' then Integer(default, 10)
|
|
106
|
+
when 'float' then Float(default)
|
|
107
|
+
when 'bool' then default == 'true'
|
|
108
|
+
else default
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
result ||= default
|
|
112
|
+
result =
|
|
83
113
|
case type
|
|
84
|
-
when 'int' then Integer(
|
|
85
|
-
when 'float' then Float(
|
|
86
|
-
when 'bool' then
|
|
87
|
-
else
|
|
114
|
+
when 'int' then Integer(Float(result).truncate)
|
|
115
|
+
when 'float' then Float(result)
|
|
116
|
+
when 'bool' then result.to_s == 'true'
|
|
117
|
+
else result
|
|
88
118
|
end
|
|
119
|
+
pmpv.new(result, default, type, memo)
|
|
89
120
|
end
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
case type
|
|
93
|
-
when 'int' then Integer(Float(result).truncate)
|
|
94
|
-
when 'float' then Float(result)
|
|
95
|
-
when 'bool' then result == 'true'
|
|
96
|
-
else result
|
|
97
|
-
end
|
|
98
|
-
pmpv.new(result, default, type, memo)
|
|
99
|
-
end
|
|
100
|
-
end.new
|
|
121
|
+
end.new
|
|
122
|
+
end
|
|
101
123
|
end
|
|
102
124
|
end.new
|
|
103
125
|
end
|
data/lib/fbe/regularly.rb
CHANGED
|
@@ -44,18 +44,18 @@ def Fbe.regularly(area, p_every_days, p_since_days = nil, fb: Fbe.fb, judge: $ju
|
|
|
44
44
|
).each.first
|
|
45
45
|
if recent
|
|
46
46
|
loog.info(
|
|
47
|
-
"#{
|
|
47
|
+
"#{judge} statistics were collected #{recent.when.ago} ago, " \
|
|
48
48
|
"skipping now (we run it every #{interval} days)"
|
|
49
49
|
)
|
|
50
50
|
return
|
|
51
51
|
end
|
|
52
|
-
loog.info("#{
|
|
52
|
+
loog.info("#{judge} statistics weren't collected for the last #{interval} days")
|
|
53
53
|
fb.txn do |fbt|
|
|
54
54
|
f = fbt.insert
|
|
55
55
|
f.what = judge
|
|
56
56
|
f.when = Time.now
|
|
57
57
|
unless p_since_days.nil?
|
|
58
|
-
days = pmp.nil? ? 28 : pmp[p_since_days].first
|
|
58
|
+
days = pmp.nil? || pmp[p_since_days].nil? ? 28 : pmp[p_since_days].first
|
|
59
59
|
since = Time.now - (days * 24 * 60 * 60)
|
|
60
60
|
f.since = since
|
|
61
61
|
end
|
data/lib/fbe/repeatedly.rb
CHANGED
|
@@ -45,7 +45,7 @@ def Fbe.repeatedly(area, p_every_hours, fb: Fbe.fb, judge: $judge, loog: $loog,
|
|
|
45
45
|
(gt when (minus (to_time (env 'TODAY' '#{Time.now.utc.iso8601}')) '#{hours} hours')))"
|
|
46
46
|
).each.first
|
|
47
47
|
if recent
|
|
48
|
-
loog.info("#{
|
|
48
|
+
loog.info("#{judge} was executed #{recent.when.ago} ago, skipping now (we run it every #{hours} hours)")
|
|
49
49
|
return
|
|
50
50
|
end
|
|
51
51
|
f = fb.query("(and (eq what '#{judge}'))").each.first
|
|
@@ -53,7 +53,7 @@ def Fbe.repeatedly(area, p_every_hours, fb: Fbe.fb, judge: $judge, loog: $loog,
|
|
|
53
53
|
f = fb.insert
|
|
54
54
|
f.what = judge
|
|
55
55
|
end
|
|
56
|
-
Fbe.overwrite(f, 'when', Time.now)
|
|
57
56
|
yield(fb.query("(and (eq what '#{judge}'))").each.first)
|
|
57
|
+
Fbe.overwrite(f, 'when', Time.now)
|
|
58
58
|
nil
|
|
59
59
|
end
|
data/lib/fbe/sec.rb
CHANGED
|
@@ -10,21 +10,20 @@ require_relative '../fbe'
|
|
|
10
10
|
#
|
|
11
11
|
# The number of seconds is taken from the +fact+ provided, usually stored
|
|
12
12
|
# there in the +seconds+ property. The seconds are formatted into a
|
|
13
|
-
# human-readable string like "
|
|
14
|
-
# tago gem.
|
|
13
|
+
# compact human-readable string like "3d" or "5h" using the tago gem.
|
|
15
14
|
#
|
|
16
15
|
# @param [Factbase::Fact] fact The fact containing the seconds property
|
|
17
16
|
# @param [String, Symbol] prop The property name with seconds (defaults to :seconds)
|
|
18
|
-
# @return [String] Human-readable time interval (e.g., "
|
|
17
|
+
# @return [String] Human-readable time interval (e.g., "2w", "3h", "5m33s")
|
|
19
18
|
# @raise [RuntimeError] If the specified property doesn't exist in the fact
|
|
20
19
|
# @note Uses the tago gem's ago method for formatting
|
|
21
20
|
# @example Format elapsed time from a fact
|
|
22
21
|
# build_fact = fb.query('(eq type "build")').first
|
|
23
22
|
# build_fact.duration = 7200 # 2 hours in seconds
|
|
24
|
-
# puts Fbe.sec(build_fact, :duration) # => "
|
|
23
|
+
# puts Fbe.sec(build_fact, :duration) # => "2h"
|
|
25
24
|
def Fbe.sec(fact, prop = :seconds)
|
|
26
25
|
s = fact[prop.to_s]
|
|
27
26
|
raise(Fbe::Error, "There is no #{prop.inspect} property") if s.nil?
|
|
28
27
|
s = Integer(s.first.to_s, 10)
|
|
29
|
-
(Time.now
|
|
28
|
+
(Time.now - s).ago
|
|
30
29
|
end
|
data/lib/fbe/tombstone.rb
CHANGED
|
@@ -56,7 +56,7 @@ class Fbe::Tombstone
|
|
|
56
56
|
n.where = where
|
|
57
57
|
n.repository = repo
|
|
58
58
|
end
|
|
59
|
-
f.public_send(:"#{@fid}=", SecureRandom.random_number(
|
|
59
|
+
f.public_send(:"#{@fid}=", SecureRandom.random_number(9_999_999_999_999)) if f[@fid].nil?
|
|
60
60
|
nn = f['issues']&.map do |ii|
|
|
61
61
|
ii.split('-').map do |i|
|
|
62
62
|
Integer(i, 10)
|
data/lib/fbe/unmask_repos.rb
CHANGED
|
@@ -24,7 +24,7 @@ require_relative 'over'
|
|
|
24
24
|
def Fbe.mask_to_regex(mask)
|
|
25
25
|
org, repo = mask.split('/')
|
|
26
26
|
raise(Fbe::Error, "Org '#{org}' can't have an asterisk") if org.include?('*')
|
|
27
|
-
Regexp.compile("#{org}/#{repo.gsub('
|
|
27
|
+
Regexp.compile("\\A#{Regexp.escape(org)}/#{Regexp.escape(repo).gsub('\\*', '.*')}\\z", Regexp::IGNORECASE)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# Resolves repository masks to actual GitHub repository names.
|
|
@@ -96,7 +96,6 @@ def Fbe.unmask_repos( # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticCompl
|
|
|
96
96
|
raise(Fbe::Error, "No repos found matching: #{options.repositories.inspect}") if repos.empty?
|
|
97
97
|
repos.shuffle!
|
|
98
98
|
loog.debug("Scanning #{repos.size} repositories: #{repos.joined}...")
|
|
99
|
-
repos.each { |repo| octo.repository(repo) }
|
|
100
99
|
return repos unless block_given?
|
|
101
100
|
repos.each do |repo|
|
|
102
101
|
break if Fbe.over?(global:, options:, loog:, epoch:, kickoff:, quota_aware:, lifetime_aware:, timeout_aware:)
|
data/lib/fbe.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fbe
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.48.
|
|
4
|
+
version: 0.48.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -356,6 +356,7 @@ files:
|
|
|
356
356
|
- ".gitignore"
|
|
357
357
|
- ".pdd"
|
|
358
358
|
- ".rubocop.yml"
|
|
359
|
+
- ".ruby-version"
|
|
359
360
|
- ".rultor.yml"
|
|
360
361
|
- ".yamllint.yml"
|
|
361
362
|
- Gemfile
|
|
@@ -386,6 +387,7 @@ files:
|
|
|
386
387
|
- lib/fbe/delete.rb
|
|
387
388
|
- lib/fbe/delete_one.rb
|
|
388
389
|
- lib/fbe/enter.rb
|
|
390
|
+
- lib/fbe/fake_octokit.rb
|
|
389
391
|
- lib/fbe/fb.rb
|
|
390
392
|
- lib/fbe/github_graph.rb
|
|
391
393
|
- lib/fbe/if_absent.rb
|
|
@@ -422,7 +424,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
422
424
|
requirements:
|
|
423
425
|
- - ">="
|
|
424
426
|
- !ruby/object:Gem::Version
|
|
425
|
-
version: '3.
|
|
427
|
+
version: '3.3'
|
|
426
428
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
427
429
|
requirements:
|
|
428
430
|
- - ">="
|