Class: Canopus::Task::ProblemMatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/canopus/task/problem_matcher.rb

Constant Summary collapse

MAX_LINE_BYTES =
65_536
MAX_PENDING_BYTES =
65_536
MAX_BUFFER_BYTES =
(16 << 20) + MAX_PENDING_BYTES
MAX_DIAGNOSTICS =
1_000
MATCH_TIMEOUT_SECONDS =
0.002
ANSI =
/\e(?:\[[0-?]*[ -\/]*[@-~]|\][^\a]*(?:\a|\e\\))/n

Instance Method Summary collapse

Constructor Details

#initialize(definition, root:, &on_change) ⇒ ProblemMatcher

Returns a new instance of ProblemMatcher.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/canopus/task/problem_matcher.rb', line 15

def initialize(definition, root:, &on_change)
  @definition = definition
  @root = File.realpath(root)
  patterns = definition.fetch("pattern")
  patterns = [patterns] unless patterns.is_a?(Array)
  @patterns = patterns.map { |pattern| compile(pattern.fetch("regexp")) }
  @watch = definition["background"]
  @begins = compile(@watch.fetch("begins_pattern")) if @watch
  @ends = compile(@watch.fetch("ends_pattern")) if @watch
  @active = !@watch || @watch.fetch("active_on_start", false)
  @on_change = on_change
  @pending = +"".b
  @cursor = 0
  @diagnostics = {}
  @diagnostic_count = 0
  @sequence = []
  @retrying_line = false
  @disabled = @discarding_line = @dirty = false
rescue SystemCallError, TypeError => error
  raise Error, "invalid problem matcher: #{error.message}"
end

Instance Method Details

#diagnostics ⇒ Object



37
38
39
# File 'lib/canopus/task/problem_matcher.rb', line 37

def diagnostics
  @snapshot ||= @diagnostics.to_h { |uri, values| [uri, values.dup.freeze] }.freeze
end

#feed(bytes, max_seconds: 0.004) ⇒ Object

Raises:



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/canopus/task/problem_matcher.rb', line 43

def feed(bytes, max_seconds: 0.004)
  raise Error, "problem matcher input must be a string" unless bytes.is_a?(String)
  unless max_seconds.is_a?(Numeric) && max_seconds.finite? && max_seconds >= 0
    raise ArgumentError, "problem matcher budget must be nonnegative and finite"
  end
  return false if @disabled

  append(bytes.b)
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + max_seconds
  while (newline = @pending.index("\n", @cursor))
    break if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
    raw = @pending.byteslice(@cursor, newline - @cursor)
    raw = raw.delete_suffix("\r")
    begin
      consume(raw)
    rescue RetryTimeout
      break
    end
    @retrying_line = false
    break if @disabled
    @cursor = newline + 1
  end
  unless pending?
    if @pending.bytesize - @cursor > MAX_PENDING_BYTES
      @pending.clear
      @cursor = 0
      @discarding_line = true
      @sequence.clear
    else
      compact_pending
    end
  end
  notify_changed
end

#finish ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/canopus/task/problem_matcher.rb', line 78

def finish
  return diagnostics if @disabled
  return diagnostics if pending?
  raw = @pending.byteslice(@cursor..)
  unless raw.nil? || raw.empty? || @discarding_line
    begin
      consume(raw)
      @retrying_line = false
    rescue RetryTimeout
      disable
    end
  end
  @pending.clear
  @cursor = 0
  @sequence.clear
  @discarding_line = false
  notify_changed
  diagnostics
end

#pending? ⇒ Boolean

Returns:

  • (Boolean)


41
# File 'lib/canopus/task/problem_matcher.rb', line 41

def pending? = !@disabled && !@pending.index("\n", @cursor).nil?