fbe 0.48.5 → 0.48.7

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.
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 && Fbe.octo(loog:, options:, global:).off_quota?(threshold: 100)
26
- loog.info('We are off GitHub quota, time to stop')
27
- return true
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
- next if before[k.to_s] == vv
47
- before[k.to_s] = vv
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
- def Fbe.pmp(fb: Fbe.fb, global: $global, options: $options, loog: $loog) # rubocop:disable Metrics/AbcSize
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
- raise Fbe::Error, "Unknown area #{area.inspect}" if node.nil?
66
- Class.new do
67
- define_method(:properties) do
68
- node.xpath('p/name').map(&:text)
69
- end
70
- others do |*args2|
71
- param = args2.first.to_s
72
- f = Fbe.fb(global:, fb:, options:, loog:).query("(and (eq what 'pmp') (eq area '#{area}'))").each.first
73
- result = f&.[](param)&.first
74
- prop = node.at_xpath("p[name='#{param}']")
75
- default = nil
76
- type = nil
77
- memo = nil
78
- if prop
79
- default = prop.at_xpath('default').text
80
- type = prop.at_xpath('type').text
81
- memo = prop.at_xpath('memo').text
82
- default =
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(default, 10)
85
- when 'float' then Float(default)
86
- when 'bool' then default == 'true'
87
- else default
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
- result ||= default
91
- result =
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,12 +44,12 @@ 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
- "#{$judge} statistics were collected #{recent.when.ago} ago, " \
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("#{$judge} statistics weren't collected for the last #{interval} days")
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
@@ -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("#{$judge} was executed #{recent.when.ago} ago, skipping now (we run it every #{hours} hours)")
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 "3 days ago" or "5 hours ago" using the
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., "2 weeks ago", "3 hours ago")
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) # => "2 hours ago"
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 + s).ago
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(99_999)) if f[@fid].nil?
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)
@@ -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('*', '.*')}", Regexp::IGNORECASE)
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
@@ -9,6 +9,6 @@
9
9
  # Copyright:: Copyright (c) 2024-2026 Zerocracy
10
10
  # License:: MIT
11
11
  module Fbe
12
- VERSION = '0.48.5' unless const_defined?(:VERSION)
12
+ VERSION = '0.48.7' unless const_defined?(:VERSION)
13
13
  class Error < StandardError; end
14
14
  end
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.5
4
+ version: 0.48.7
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.2'
427
+ version: '3.3'
426
428
  required_rubygems_version: !ruby/object:Gem::Requirement
427
429
  requirements:
428
430
  - - ">="