thread_weaver 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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +19 -0
  3. data/.gitignore +59 -0
  4. data/.rspec +3 -0
  5. data/.vscode/settings.json +14 -0
  6. data/CHANGELOG.md +7 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE +21 -0
  9. data/README.md +37 -0
  10. data/Rakefile +13 -0
  11. data/bin/console +15 -0
  12. data/bin/gem_smoke_test +10 -0
  13. data/bin/setup +8 -0
  14. data/examples/always_deadlocks.rb +15 -0
  15. data/examples/takes_a_while.rb +7 -0
  16. data/examples/thread_safe_nonblocking_run_at_most_once.rb +23 -0
  17. data/examples/thread_safe_run_at_most_once.rb +19 -0
  18. data/examples/thread_unsafe_run_at_most_once.rb +17 -0
  19. data/lib/thread_weaver.rb +15 -0
  20. data/lib/thread_weaver/controllable_thread.rb +177 -0
  21. data/lib/thread_weaver/iterative_race_detector.rb +178 -0
  22. data/lib/thread_weaver/thread_instruction.rb +68 -0
  23. data/lib/thread_weaver/version.rb +6 -0
  24. data/sorbet/config +2 -0
  25. data/sorbet/rbi/gems/ast.rbi +48 -0
  26. data/sorbet/rbi/gems/coderay.rbi +285 -0
  27. data/sorbet/rbi/gems/method_source.rbi +64 -0
  28. data/sorbet/rbi/gems/parallel.rbi +83 -0
  29. data/sorbet/rbi/gems/parser.rbi +1510 -0
  30. data/sorbet/rbi/gems/pry-nav.rbi +29 -0
  31. data/sorbet/rbi/gems/pry.rbi +1965 -0
  32. data/sorbet/rbi/gems/rainbow.rbi +118 -0
  33. data/sorbet/rbi/gems/rake.rbi +645 -0
  34. data/sorbet/rbi/gems/regexp_parser.rbi +920 -0
  35. data/sorbet/rbi/gems/rexml.rbi +589 -0
  36. data/sorbet/rbi/gems/rspec-core.rbi +1893 -0
  37. data/sorbet/rbi/gems/rspec-expectations.rbi +1148 -0
  38. data/sorbet/rbi/gems/rspec-mocks.rbi +1091 -0
  39. data/sorbet/rbi/gems/rspec-support.rbi +280 -0
  40. data/sorbet/rbi/gems/rspec.rbi +15 -0
  41. data/sorbet/rbi/gems/rubocop-ast.rbi +1351 -0
  42. data/sorbet/rbi/gems/rubocop-performance.rbi +471 -0
  43. data/sorbet/rbi/gems/rubocop.rbi +7510 -0
  44. data/sorbet/rbi/gems/ruby-progressbar.rbi +305 -0
  45. data/sorbet/rbi/gems/standard.rbi +141 -0
  46. data/sorbet/rbi/gems/unicode-display_width.rbi +17 -0
  47. data/sorbet/rbi/hidden-definitions/errors.txt +4309 -0
  48. data/sorbet/rbi/hidden-definitions/hidden.rbi +8622 -0
  49. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +276 -0
  50. data/sorbet/rbi/sorbet-typed/lib/rubocop-performance/~>1.6/rubocop-performance.rbi +149 -0
  51. data/sorbet/rbi/todo.rbi +6 -0
  52. data/thread_weaver.gemspec +34 -0
  53. metadata +111 -0
@@ -0,0 +1,178 @@
1
+ # typed: strict
2
+
3
+ module ThreadWeaver
4
+ class RaceConditionDetectedError < Error; end
5
+
6
+ class DeadlockDetectedError < Error; end
7
+
8
+ class BlockingSynchronizationDetected < Error; end
9
+
10
+ DEADLOCK_MIGHT_BE_CONFIG = T.let(
11
+ "Either there is a deadlock, or assume_deadlocked_after_ms is set too low. Try increasing "\
12
+ "assume_deadlocked_after_ms to a higher value.",
13
+ String
14
+ )
15
+
16
+ class IterativeRaceDetector
17
+ extend T::Sig
18
+
19
+ sig do
20
+ params(
21
+ setup: T.proc.returns(T.untyped),
22
+ run: T.proc.params(arg0: T.untyped).void,
23
+ check: T.proc.params(arg0: T.untyped).returns(T::Boolean),
24
+ target_classes: T::Array[Module],
25
+ assume_deadlocked_after_ms: Integer,
26
+ run_secondary: T.nilable(T.proc.params(arg0: T.untyped).void),
27
+ expect_nonblocking: T::Boolean
28
+ ).void
29
+ end
30
+ def initialize(setup:, run:, check:, target_classes:, assume_deadlocked_after_ms:, run_secondary: nil, expect_nonblocking: false)
31
+ @setup_blk = T.let(setup, T.proc.returns(T.untyped))
32
+ @check_blk = T.let(check, T.proc.params(arg0: T.untyped).returns(T::Boolean))
33
+ @target_classes = T.let(target_classes, T::Array[Module])
34
+ @expect_nonblocking = T.let(expect_nonblocking, T::Boolean)
35
+
36
+ @run_blk = T.let(run, T.proc.params(arg0: T.untyped).void)
37
+ # Secondary is optional as the common case will be testing two identical blocks of code
38
+ @run_secondary_blk = T.let(run_secondary || run, T.proc.params(arg0: T.untyped).void)
39
+
40
+ @assume_deadlocked_after = T.let(assume_deadlocked_after_ms / 1000.0, Float)
41
+
42
+ @scenarios_run = T.let(0, Integer)
43
+ @secondary_deadlocked_count = T.let(0, Integer)
44
+ end
45
+
46
+ sig { void }
47
+ def run
48
+ check_if_can_run_standalone(@run_blk, "run")
49
+ if @run_secondary_blk != @run_blk
50
+ check_if_can_run_standalone(@run_secondary_blk, "run_secondary")
51
+ end
52
+
53
+ hold_primary_at_line_count = -1
54
+ done = T.let(false, T::Boolean)
55
+
56
+ error_encountered = T.let(nil, T.nilable(Exception))
57
+
58
+ until done
59
+ Timeout.timeout(2 * @assume_deadlocked_after) do
60
+ hold_primary_at_line_count += 1
61
+
62
+ context = @setup_blk.call
63
+
64
+ primary_thread = ControllableThread.new(context, name: "primary_thread", &@run_blk)
65
+
66
+ # Pause the primary thread after it executes hold_primary_at_line_count number of lines
67
+ primary_thread.set_and_wait_for_next_instruction(
68
+ PauseWhenLineCount.new(
69
+ count: hold_primary_at_line_count,
70
+ target_classes: @target_classes
71
+ )
72
+ )
73
+
74
+ # Start secondary thread and instruct it to run until completion. The primary thread is
75
+ # still paused part-way through its execution
76
+ secondary_thread = ControllableThread.new(context, name: "secondary_thread", &@run_secondary_blk)
77
+ secondary_thread.set_next_instruction(ContinueToThreadEnd.new)
78
+
79
+ if check_for_deadlock(secondary_thread)
80
+ # At this point, it appears that the secondary thread is deadlocked. This could be
81
+ # because of a true deadlock, but this also is expected to happen even in thread-safe
82
+ # code that uses blocking locks. If blocking locks are used then its quite likely that
83
+ # pausing the primary thread at certain points might would block the secondary thread.
84
+ # For that reason, this isn't considered an outright error.
85
+ @secondary_deadlocked_count += 1
86
+ primary_thread.set_and_wait_for_next_instruction(ContinueToThreadEnd.new)
87
+
88
+ if @expect_nonblocking
89
+ error_encountered ||= BlockingSynchronizationDetected.new(
90
+ "Deadlock detected, but expect_nonblocking was set to true. Make sure you aren't "\
91
+ "blocking waiting for a lock."
92
+ )
93
+ end
94
+ end
95
+
96
+ # Wait for the secondary thread to complete, taking note of any errors
97
+ begin
98
+ secondary_thread.join
99
+ rescue => e
100
+ # Defer exception until after the primary thread gets a chance to join, to avoid leaking
101
+ # threads
102
+ error_encountered ||= e
103
+ end
104
+
105
+ # Only now that the secondary thread has completed, release the primary thread
106
+ primary_thread.set_and_wait_for_next_instruction(ContinueToThreadEnd.new)
107
+
108
+ begin
109
+ primary_thread.join
110
+ @scenarios_run += 1
111
+ rescue ThreadCompletedEarlyError
112
+ # ThreadCompletedEarlyError will occur if the primary thread never successfully paused
113
+ # at the specified location. This happens normally, when hold_primary_at_line_count is
114
+ # incremented until it exceeds the number of lines actually executed in the primary
115
+ # thread. Once that point is reached, the primary thread will never get caught on the
116
+ # PauseWhenLineCount instruction. ControllableThread signals failures to execute a given
117
+ # instruction by returning a ThreadCompletedEarlyError, which we use as a signal to stop
118
+ # probing for race conditions. When this happens, we have done a race condition check
119
+ # with the primary thread paused at every possible location in target_classes, assuming
120
+ # the code in question is deterministic, so there is nothing left to check.
121
+ done = true
122
+ end
123
+
124
+ # Now that both threads have had a chance to join, raise any errors discovered
125
+ raise error_encountered if error_encountered
126
+
127
+ check_passed = @check_blk.call(context)
128
+
129
+ unless check_passed
130
+ raise RaceConditionDetectedError.new("Test failed")
131
+ end
132
+ end
133
+ end
134
+
135
+ if @secondary_deadlocked_count == @scenarios_run
136
+ message = "In every scenario, the secondary thread was assumed deadlocked while the "\
137
+ "primary thread was paused. #{DEADLOCK_MIGHT_BE_CONFIG}"
138
+ raise DeadlockDetectedError.new(message)
139
+ end
140
+ end
141
+
142
+ sig { params(blk: T.proc.params(arg0: T.untyped).void, name: String).void }
143
+ def check_if_can_run_standalone(blk, name)
144
+ context = @setup_blk.call
145
+ thread = ControllableThread.new(context, name: name, &blk)
146
+ thread.set_next_instruction(ContinueToThreadEnd.new)
147
+
148
+ if check_for_deadlock(thread)
149
+ thread.kill
150
+ message = "#{name} appears to be deadlocked when running alone, with no other concurrent "\
151
+ "threads. #{DEADLOCK_MIGHT_BE_CONFIG}"
152
+
153
+ raise DeadlockDetectedError.new(message)
154
+ end
155
+
156
+ check_passed = @check_blk.call(context)
157
+
158
+ unless check_passed
159
+ message = "#{name} failed check when running alone, with no other concurrent threads. "\
160
+ "Your check logic may be flawed."
161
+ raise RaceConditionDetectedError.new(message)
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ sig { params(thread: ControllableThread).returns(T::Boolean) }
168
+ def check_for_deadlock(thread)
169
+ started_at = Time.now
170
+
171
+ while thread.alive? && (Time.now - started_at) < @assume_deadlocked_after
172
+ Thread.pass
173
+ end
174
+
175
+ thread.alive?
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,68 @@
1
+ # typed: strict
2
+
3
+ module ThreadWeaver
4
+ module ThreadInstruction
5
+ extend T::Sig
6
+ extend T::Helpers
7
+ interface!
8
+ sealed!
9
+
10
+ sig { abstract.params(m: Module).returns(T::Boolean) }
11
+ def is_a?(m)
12
+ end
13
+ end
14
+
15
+ class PauseAtThreadStart < T::Struct
16
+ include ThreadInstruction
17
+ end
18
+
19
+ class ContinueToThreadEnd < T::Struct
20
+ include ThreadInstruction
21
+ end
22
+
23
+ class PauseWhenLineCount < T::Struct
24
+ include ThreadInstruction
25
+ extend T::Sig
26
+
27
+ const :count, Integer
28
+ const :target_classes, T::Array[Module]
29
+
30
+ sig { returns(PauseWhenLineCount) }
31
+ def next
32
+ PauseWhenLineCount.new(
33
+ count: count + 1,
34
+ target_classes: target_classes
35
+ )
36
+ end
37
+ end
38
+
39
+ class PauseAtMethodCall < T::Struct
40
+ include ThreadInstruction
41
+
42
+ const :klass, Class
43
+ const :method_name, Symbol
44
+ end
45
+
46
+ class PauseAtMethodReturn < T::Struct
47
+ include ThreadInstruction
48
+
49
+ const :klass, Class
50
+ const :method_name, Symbol
51
+ end
52
+
53
+ class PauseAtSourceLine < T::Struct
54
+ include ThreadInstruction
55
+ extend T::Sig
56
+
57
+ const :path_suffix, String
58
+ const :line, Integer
59
+
60
+ sig { returns(PauseAtSourceLine) }
61
+ def next
62
+ PauseAtSourceLine.new(
63
+ line: line + 1,
64
+ path_suffix: path_suffix
65
+ )
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,6 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ThreadWeaver
5
+ VERSION = "0.1.0"
6
+ end
@@ -0,0 +1,2 @@
1
+ --dir
2
+ .
@@ -0,0 +1,48 @@
1
+ # This file is autogenerated. Do not edit it by hand. Regenerate it with:
2
+ # srb rbi gems
3
+
4
+ # typed: strict
5
+ #
6
+ # If you would like to make changes to this file, great! Please create the gem's shim here:
7
+ #
8
+ # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/ast/all/ast.rbi
9
+ #
10
+ # ast-2.4.1
11
+
12
+ module AST
13
+ end
14
+ class AST::Node
15
+ def +(array); end
16
+ def <<(element); end
17
+ def ==(other); end
18
+ def append(element); end
19
+ def assign_properties(properties); end
20
+ def children; end
21
+ def clone; end
22
+ def concat(array); end
23
+ def dup; end
24
+ def eql?(other); end
25
+ def fancy_type; end
26
+ def hash; end
27
+ def initialize(type, children = nil, properties = nil); end
28
+ def inspect(indent = nil); end
29
+ def original_dup; end
30
+ def to_a; end
31
+ def to_ast; end
32
+ def to_s(indent = nil); end
33
+ def to_sexp(indent = nil); end
34
+ def to_sexp_array; end
35
+ def type; end
36
+ def updated(type = nil, children = nil, properties = nil); end
37
+ end
38
+ class AST::Processor
39
+ include AST::Processor::Mixin
40
+ end
41
+ module AST::Processor::Mixin
42
+ def handler_missing(node); end
43
+ def process(node); end
44
+ def process_all(nodes); end
45
+ end
46
+ module AST::Sexp
47
+ def s(type, *children); end
48
+ end
@@ -0,0 +1,285 @@
1
+ # This file is autogenerated. Do not edit it by hand. Regenerate it with:
2
+ # srb rbi gems
3
+
4
+ # typed: true
5
+ #
6
+ # If you would like to make changes to this file, great! Please create the gem's shim here:
7
+ #
8
+ # https://github.com/sorbet/sorbet-typed/new/master?filename=lib/coderay/all/coderay.rbi
9
+ #
10
+ # coderay-1.1.3
11
+
12
+ module CodeRay
13
+ def self.coderay_path(*path); end
14
+ def self.encode(code, lang, format, options = nil); end
15
+ def self.encode_file(filename, format, options = nil); end
16
+ def self.encode_tokens(tokens, format, options = nil); end
17
+ def self.encoder(format, options = nil); end
18
+ def self.get_scanner_options(options); end
19
+ def self.highlight(code, lang, options = nil, format = nil); end
20
+ def self.highlight_file(filename, options = nil, format = nil); end
21
+ def self.scan(code, lang, options = nil, &block); end
22
+ def self.scan_file(filename, lang = nil, options = nil, &block); end
23
+ def self.scanner(lang, options = nil, &block); end
24
+ end
25
+ module CodeRay::FileType
26
+ def self.[](filename, read_shebang = nil); end
27
+ def self.fetch(filename, default = nil, read_shebang = nil); end
28
+ def self.type_from_shebang(filename); end
29
+ end
30
+ class CodeRay::FileType::UnknownFileType < Exception
31
+ end
32
+ class CodeRay::Tokens < Array
33
+ def begin_group(kind); end
34
+ def begin_line(kind); end
35
+ def count; end
36
+ def encode(encoder, options = nil); end
37
+ def end_group(kind); end
38
+ def end_line(kind); end
39
+ def method_missing(meth, options = nil); end
40
+ def scanner; end
41
+ def scanner=(arg0); end
42
+ def split_into_parts(*sizes); end
43
+ def text_token(*arg0); end
44
+ def to_s; end
45
+ def tokens(*arg0); end
46
+ end
47
+ class CodeRay::TokensProxy
48
+ def block; end
49
+ def block=(arg0); end
50
+ def each(*args, &blk); end
51
+ def encode(encoder, options = nil); end
52
+ def initialize(input, lang, options = nil, block = nil); end
53
+ def input; end
54
+ def input=(arg0); end
55
+ def lang; end
56
+ def lang=(arg0); end
57
+ def method_missing(method, *args, &blk); end
58
+ def options; end
59
+ def options=(arg0); end
60
+ def scanner; end
61
+ def tokens; end
62
+ end
63
+ module CodeRay::PluginHost
64
+ def [](id, *args, &blk); end
65
+ def all_plugins; end
66
+ def const_missing(const); end
67
+ def default(id = nil); end
68
+ def list; end
69
+ def load(id, *args, &blk); end
70
+ def load_all; end
71
+ def load_plugin_map; end
72
+ def make_plugin_hash; end
73
+ def map(hash); end
74
+ def path_to(plugin_id); end
75
+ def plugin_hash; end
76
+ def plugin_path(*args); end
77
+ def register(plugin, id); end
78
+ def self.extended(mod); end
79
+ def validate_id(id); end
80
+ end
81
+ class CodeRay::PluginHost::PluginNotFound < LoadError
82
+ end
83
+ class CodeRay::PluginHost::HostNotFound < LoadError
84
+ end
85
+ module CodeRay::Plugin
86
+ def aliases; end
87
+ def plugin_host(host = nil); end
88
+ def plugin_id; end
89
+ def register_for(id); end
90
+ def title(title = nil); end
91
+ end
92
+ module CodeRay::Scanners
93
+ extend CodeRay::PluginHost
94
+ end
95
+ class CodeRay::Scanners::Scanner < StringScanner
96
+ def binary_string; end
97
+ def column(pos = nil); end
98
+ def each(&block); end
99
+ def file_extension; end
100
+ def initialize(code = nil, options = nil); end
101
+ def lang; end
102
+ def line(pos = nil); end
103
+ def raise_inspect(message, tokens, state = nil, ambit = nil, backtrace = nil); end
104
+ def raise_inspect_arguments(message, tokens, state, ambit); end
105
+ def reset; end
106
+ def reset_instance; end
107
+ def scan_rest; end
108
+ def scan_tokens(tokens, options); end
109
+ def scanner_state_info(state); end
110
+ def self.encode_with_encoding(code, target_encoding); end
111
+ def self.encoding(name = nil); end
112
+ def self.file_extension(extension = nil); end
113
+ def self.guess_encoding(s); end
114
+ def self.lang; end
115
+ def self.normalize(code); end
116
+ def self.to_unix(code); end
117
+ def set_string_from_source(source); end
118
+ def set_tokens_from_options(options); end
119
+ def setup; end
120
+ def state; end
121
+ def state=(arg0); end
122
+ def string=(code); end
123
+ def tokenize(source = nil, options = nil); end
124
+ def tokens; end
125
+ def tokens_last(tokens, n); end
126
+ def tokens_size(tokens); end
127
+ extend CodeRay::Plugin
128
+ include Enumerable
129
+ end
130
+ class CodeRay::Scanners::Scanner::ScanError < StandardError
131
+ end
132
+ class CodeRay::WordList < Hash
133
+ def add(words, value = nil); end
134
+ def initialize(default = nil); end
135
+ end
136
+ class CodeRay::WordList::CaseIgnoring < CodeRay::WordList
137
+ def [](key); end
138
+ def []=(key, value); end
139
+ end
140
+ module CodeRay::Scanners::Java::BuiltinTypes
141
+ end
142
+ class CodeRay::Scanners::Java < CodeRay::Scanners::Scanner
143
+ def scan_tokens(encoder, options); end
144
+ end
145
+ class CodeRay::Scanners::Ruby < CodeRay::Scanners::Scanner
146
+ def interpreted_string_state; end
147
+ def scan_tokens(encoder, options); end
148
+ def setup; end
149
+ end
150
+ module CodeRay::Scanners::Ruby::Patterns
151
+ end
152
+ class Anonymous_Struct_1 < Struct
153
+ def delim; end
154
+ def delim=(_); end
155
+ def heredoc; end
156
+ def heredoc=(_); end
157
+ def interpreted; end
158
+ def interpreted=(_); end
159
+ def next_state; end
160
+ def next_state=(_); end
161
+ def opening_paren; end
162
+ def opening_paren=(_); end
163
+ def paren_depth; end
164
+ def paren_depth=(_); end
165
+ def pattern; end
166
+ def pattern=(_); end
167
+ def self.[](*arg0); end
168
+ def self.inspect; end
169
+ def self.members; end
170
+ def self.new(*arg0); end
171
+ def type; end
172
+ def type=(_); end
173
+ end
174
+ class CodeRay::Scanners::Ruby::StringState < Anonymous_Struct_1
175
+ def heredoc_pattern(delim, interpreted, indented); end
176
+ def initialize(kind, interpreted, delim, heredoc = nil); end
177
+ def self.simple_key_pattern(delim); end
178
+ end
179
+ module CodeRay::Encoders
180
+ extend CodeRay::PluginHost
181
+ end
182
+ class CodeRay::Encoders::Encoder
183
+ def <<(token); end
184
+ def begin_group(kind); end
185
+ def begin_line(kind); end
186
+ def compile(tokens, options = nil); end
187
+ def encode(code, lang, options = nil); end
188
+ def encode_tokens(tokens, options = nil); end
189
+ def end_group(kind); end
190
+ def end_line(kind); end
191
+ def file_extension; end
192
+ def finish(options); end
193
+ def get_output(options); end
194
+ def highlight(code, lang, options = nil); end
195
+ def initialize(options = nil); end
196
+ def options; end
197
+ def options=(arg0); end
198
+ def output(data); end
199
+ def scanner; end
200
+ def scanner=(arg0); end
201
+ def self.const_missing(sym); end
202
+ def self.file_extension; end
203
+ def setup(options); end
204
+ def text_token(text, kind); end
205
+ def token(content, kind); end
206
+ def tokens(tokens, options = nil); end
207
+ extend CodeRay::Plugin
208
+ end
209
+ class CodeRay::Encoders::HTML < CodeRay::Encoders::Encoder
210
+ def begin_group(kind); end
211
+ def begin_line(kind); end
212
+ def break_lines(text, style); end
213
+ def check_group_nesting(name, kind); end
214
+ def check_options!(options); end
215
+ def close_span; end
216
+ def css; end
217
+ def css_class_for_kinds(kinds); end
218
+ def end_group(kind); end
219
+ def end_line(kind); end
220
+ def finish(options); end
221
+ def make_span_for_kinds(method, hint); end
222
+ def self.make_html_escape_hash; end
223
+ def self.token_path_to_hint(hint, kinds); end
224
+ def setup(options); end
225
+ def style_for_kinds(kinds); end
226
+ def text_token(text, kind); end
227
+ end
228
+ module CodeRay::Encoders::HTML::Output
229
+ def apply_title!(title); end
230
+ def css; end
231
+ def css=(arg0); end
232
+ def self.extended(o); end
233
+ def self.make_stylesheet(css, in_tag = nil); end
234
+ def self.page_template_for_css(css); end
235
+ def stylesheet(in_tag = nil); end
236
+ def wrap!(element, *args); end
237
+ def wrap_in!(template); end
238
+ def wrapped_in; end
239
+ def wrapped_in=(arg0); end
240
+ def wrapped_in?(element); end
241
+ end
242
+ class CodeRay::Encoders::HTML::Output::Template < String
243
+ def apply(target, replacement); end
244
+ def self.wrap!(str, template, target); end
245
+ end
246
+ class CodeRay::Encoders::HTML::CSS
247
+ def get_style_for_css_classes(css_classes); end
248
+ def initialize(style = nil); end
249
+ def parse(stylesheet); end
250
+ def self.load_stylesheet(style = nil); end
251
+ def stylesheet; end
252
+ end
253
+ module CodeRay::Encoders::HTML::Numbering
254
+ def self.number!(output, mode = nil, options = nil); end
255
+ end
256
+ module CodeRay::Styles
257
+ extend CodeRay::PluginHost
258
+ end
259
+ class CodeRay::Styles::Style
260
+ extend CodeRay::Plugin
261
+ end
262
+ class CodeRay::Duo
263
+ def call(code, options = nil); end
264
+ def encode(code, options = nil); end
265
+ def encoder; end
266
+ def format; end
267
+ def format=(arg0); end
268
+ def highlight(code, options = nil); end
269
+ def initialize(lang = nil, format = nil, options = nil); end
270
+ def lang; end
271
+ def lang=(arg0); end
272
+ def options; end
273
+ def options=(arg0); end
274
+ def scanner; end
275
+ def self.[](*arg0); end
276
+ end
277
+ class CodeRay::Encoders::Terminal < CodeRay::Encoders::Encoder
278
+ def begin_group(kind); end
279
+ def begin_line(kind); end
280
+ def end_group(kind); end
281
+ def end_line(kind); end
282
+ def open_token(kind); end
283
+ def setup(options); end
284
+ def text_token(text, kind); end
285
+ end