steadfast 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8abea2bc78eddf9d3a4785cbb385799f1ea1ec21e11b794cfc32f8b13b5a8639
4
+ data.tar.gz: 2ddf099e67ee8725df09a644bca9a25a26031c17363b10bdecea7b8e73511487
5
+ SHA512:
6
+ metadata.gz: 93299385d7b4098bdb409495d57c142de725e6328cf185f5e0a27d087b8e2e35656c31dd433d3324396f6959b8ec4c0114bbf69911788f2aefcedcb6b12d77fb
7
+ data.tar.gz: 04c5a30cc22f2ff6268a6fb0345c39a74632ccd559b215280dac020a5717f34802d9e8e669588a23ee21bdca040dca9d308a6153b25bcbf9896fcb50d63b397d
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.0.1 2026-11-06
2
+
3
+ - Implement Steadfast.to_source
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # steadfast
2
+ A modern meta-programming gem for Ruby
@@ -0,0 +1,652 @@
1
+ module Steadfast
2
+ class Sourcifier < Ruby::Prism::BasicVisitor
3
+ VISIT_PLANS = {
4
+ alias_global_variable: [:keyword_loc, :whitespace, :new_name, :whitespace, :old_name],
5
+ alias_method: [:keyword_loc, :whitespace, :new_name, :whitespace, :old_name],
6
+ and: [:left, :whitespace, :operator_loc, :whitespace, :right],
7
+ assoc: [:key, :whitespace, :operator_loc, :whitespace, :value],
8
+ assoc_splat: [:operator_loc, :value],
9
+ back_reference_read: :emit_verbatim,
10
+ block: [:opening_loc, :parameters, :newline, :body, :newline, :closing_loc],
11
+ block_argument: [:operator_loc, :expression],
12
+ block_local_variable: :emit_verbatim,
13
+ block_parameter: [:operator_loc, :name_loc],
14
+ break: [:keyword_loc, :whitespace, :arguments],
15
+ call_target: [:receiver, :call_operator_loc, :message_loc],
16
+ capture_pattern: [:value, :whitespace, :operator_loc, :whitespace, :target],
17
+ class: [:class_keyword_loc, :whitespace, :constant_path, :whitespace,
18
+ :inheritance_operator_loc, :whitespace, :superclass, :newline,
19
+ :body, :newline, :end_keyword_loc],
20
+ class_variable_read: :emit_verbatim,
21
+ class_variable_target: :emit_verbatim,
22
+ class_variable_write: [:name_loc, :operator_loc, :value],
23
+ constant_path: [:parent, :delimiter_loc, :name_loc],
24
+ constant_path_write: [:target, :operator_loc, :value],
25
+ constant_read: :emit_verbatim,
26
+ constant_write: [:name_loc, :operator_loc, :value],
27
+ defined: [:keyword_loc, :lparen_loc, :value, :rparen_loc],
28
+ else: [:else_keyword_loc, :newline, :statements],
29
+ embedded_statements: [:opening_loc, :statements, :closing_loc],
30
+ embedded_variable: [:operator_loc, :variable],
31
+ ensure: [:ensure_keyword_loc, :newline, :statements],
32
+ false: :emit_verbatim,
33
+ flip_flop: [:left, :operator_loc, :right],
34
+ float: :emit_verbatim,
35
+ for: [:for_keyword_loc, :whitespace, :index, :whitespace, :in_keyword_loc,
36
+ :whitespace, :collection, :whitespace, :do_keyword_loc, :newline,
37
+ :statements, :newline, :end_keyword_loc],
38
+ forwarding_arguments: :emit_verbatim,
39
+ forwarding_parameter: :emit_verbatim,
40
+ forwarding_super: :emit_verbatim,
41
+ global_variable_read: :emit_verbatim,
42
+ global_variable_target: :emit_verbatim,
43
+ global_variable_write: [:name_loc, :operator_loc, :value],
44
+ imaginary: :emit_verbatim,
45
+ implicit: :emit_nothing,
46
+ implicit_rest: :emit_nothing,
47
+ in: [:in_loc, :whitespace, :pattern, :whitespace, :then_loc],
48
+ index_target: [:receiver, :opening_loc, :arguments, :closing_loc],
49
+ instance_variable_operator_write: [:name_loc, :whitespace, :binary_operator_loc, :whitespace, :value],
50
+ instance_variable_or_write: [:name_loc, :whitespace, :operator_loc, :whitespace, :value],
51
+ instance_variable_read: :emit_verbatim,
52
+ instance_variable_target: :emit_verbatim,
53
+ instance_variable_write: [:name_loc, :operator_loc, :value],
54
+ integer: :emit_verbatim,
55
+ it_parameters: :emit_nothing,
56
+ it_local_variable_read: :emit_verbatim,
57
+ keyword_rest_parameter: [:operator_loc, :name_loc],
58
+ keyword_parameter: :emit_verbatim,
59
+ lambda: [:operator_loc, :parameters, :whitespace, :opening_loc, :newline, :body, :newline, :closing_loc],
60
+ local_variable_and_write: [:name_loc, :operator_loc, :value],
61
+ local_variable_operator_write: [:name_loc, :binary_operator_loc, :value],
62
+ local_variable_or_write: [:name_loc, :operator_loc, :value],
63
+ local_variable_read: :emit_verbatim,
64
+ local_variable_target: :emit_verbatim,
65
+ local_variable_write: [:name_loc, :operator_loc, :value],
66
+ match_predicate: [:value, :whitespace, :operator_loc, :whitespace, :pattern],
67
+ match_required: [:value, :whitespace, :operator_loc, :whitespace, :pattern],
68
+ match_write: [:call],
69
+ module: [:module_keyword_loc, :whitespace, :constant_path, :newline, :body, :newline, :end_keyword_loc],
70
+ next: [:keyword_loc, :whitespace, :arguments],
71
+ nil: :emit_verbatim,
72
+ no_keywords_parameter: :emit_verbatim,
73
+ numbered_parameters: :emit_nothing,
74
+ optional_parameter: [:name_loc, :whitespace, :operator_loc, :whitespace, :value],
75
+ optional_keyword_parameter: [:name_loc, :whitespace, :value],
76
+ or: [:left, :whitespace, :operator_loc, :whitespace, :right],
77
+ parentheses: [:opening_loc, :body, :closing_loc],
78
+ pinned_expression: [:operator_loc, :lparen_loc, :expression, :rparen_loc],
79
+ pinned_variable: [:operator_loc, :variable],
80
+ rational: :emit_verbatim,
81
+ redo: :emit_verbatim,
82
+ regular_expression: :emit_verbatim,
83
+ required_parameter: :emit_verbatim,
84
+ required_keyword_parameter: :emit_verbatim,
85
+ rescue_modifier: [:expression, :whitespace, :keyword_loc, :whitespace, :rescue_expression],
86
+ rest_parameter: [:operator_loc, :name_loc],
87
+ retry: :emit_verbatim,
88
+ return: [:keyword_loc, :whitespace, :arguments],
89
+ self: :emit_verbatim,
90
+ singleton_class: [:class_keyword_loc, :whitespace, :operator_loc, :whitespace, :expression,
91
+ :newline, :body, :newline, :end_keyword_loc],
92
+ source_encoding: :emit_verbatim,
93
+ source_file: :emit_verbatim,
94
+ source_line: :emit_verbatim,
95
+ splat: [:operator_loc, :expression],
96
+ symbol: :emit_verbatim,
97
+ true: :emit_verbatim,
98
+ x_string: :emit_verbatim,
99
+ yield: [:keyword_loc, :lparen_loc, :arguments, :rparen_loc]
100
+ }
101
+
102
+ attr_reader :buffer
103
+
104
+ def initialize(**)
105
+ @buffer = +''
106
+ end
107
+
108
+ def to_source(node)
109
+ @buffer.clear
110
+ visit(node)
111
+ @buffer
112
+ end
113
+
114
+ VISIT_PLANS.each do |key, plan|
115
+ sym = :"visit_#{key}_node"
116
+ define_method(sym) { |n| visit_plan(plan, n) }
117
+ end
118
+
119
+ def visit_array_node(node)
120
+ emit_code(node.opening_loc)
121
+ if node.opening_loc && node.opening_loc.slice =~ /^%/
122
+ visit_space_separated_nodes(node.elements)
123
+ else
124
+ visit_comma_separated_nodes(node.elements)
125
+ end
126
+ emit_code(node.closing_loc)
127
+ end
128
+
129
+ def visit_array_pattern_node(node)
130
+ emit_code(node.constant)
131
+ emit_code(node.opening_loc)
132
+ comma = visit_comma_separated_nodes(node.requireds, comma)
133
+ if node.rest
134
+ emit_comma if comma
135
+ emit_code(node.rest)
136
+ comma = true
137
+ end
138
+ visit_comma_separated_nodes(node.posts, comma)
139
+ emit_code(node.closing_loc)
140
+ end
141
+
142
+ def visit_arguments_node(node, subscript = 0..-1)
143
+ visit_comma_separated_nodes(node.arguments[subscript])
144
+ end
145
+
146
+ def visit_begin_node(node)
147
+ emit_code(node.begin_keyword_loc)
148
+ emit_newline
149
+ emit_code(node.statements)
150
+ emit_newline
151
+ emit_code(node.rescue_clause)
152
+ emit_newline
153
+ emit_code(node.else_clause)
154
+ emit_newline
155
+ emit_code(node.ensure_clause)
156
+ emit_newline
157
+ emit_code(node.end_keyword_loc) if node.begin_keyword_loc
158
+ end
159
+
160
+ def visit_block_parameters_node(node)
161
+ emit_code(node.opening_loc)
162
+ emit_code(node.parameters)
163
+ emit_semicolon if node.locals && node.locals.size > 0
164
+ visit_comma_separated_nodes(node.locals)
165
+ emit_code(node.closing_loc)
166
+ end
167
+
168
+ def visit_call_node(node)
169
+ if (node.receiver && !node.call_operator_loc && !node.arguments && node.name != :[]) ||
170
+ (node.receiver && !node.call_operator_loc && node.name == :!)
171
+ return visit_call_node_unary_op(node)
172
+ end
173
+
174
+ return visit_call_node_attribute_write(node) if node.attribute_write?
175
+
176
+ block = node.block
177
+
178
+ emit_code(node.receiver)
179
+ emit_code(node.call_operator_loc)
180
+ if (ml = node.message_loc)
181
+ ol = node.opening_loc
182
+ emit_message_loc = !ol || (ol.start_line != ml.start_line) || (ol.start_column != ml.start_column)
183
+ emit_whitespace if node.name =~ /^\+|\-|\*|\/|\&|\|\*\*|\&\&|\|\||\%$/
184
+ emit_code(node.message_loc) if emit_message_loc
185
+ end
186
+ emit_code(node.opening_loc)
187
+ emit_whitespace if !node.opening_loc && node.arguments
188
+ emit_code(node.arguments)
189
+
190
+ if block.is_a?(Ruby::Prism::BlockArgumentNode)
191
+ emit_comma if node.arguments && node.arguments.arguments.size > 0
192
+ emit_whitespace if !node.arguments
193
+ emit_code(block)
194
+ block = nil
195
+ end
196
+ emit_code(node.closing_loc)
197
+ emit_semicolon if !node.opening_loc && node.arguments && node.arguments.arguments.size == 1 && node.arguments.arguments[0].is_a?(Ruby::Prism::SplatNode) && !block
198
+ emit_whitespace if block
199
+ emit_code(block)
200
+ end
201
+
202
+ def visit_call_node_attribute_write(node)
203
+ emit_code(node.receiver)
204
+ if node.call_operator_loc
205
+ emit_code(node.call_operator_loc)
206
+ emit_code(node.message_loc)
207
+ end
208
+ emit_code(node.opening_loc)
209
+ comma = visit_arguments_node(node.arguments, 0..-2)
210
+ if node.block
211
+ emit_comma if comma
212
+ emit_code(node.block)
213
+ end
214
+ emit_code(node.closing_loc)
215
+ emit(" = ")
216
+ emit_code(node.arguments.arguments[-1])
217
+ return
218
+ end
219
+
220
+ def visit_call_node_unary_op(node)
221
+ emit_code(node.message_loc)
222
+ emit_code(node.opening_loc)
223
+ emit_whitespace if !node.opening_loc
224
+ emit_code(node.receiver)
225
+ emit_code(node.closing_loc)
226
+ end
227
+
228
+ def visit_case_node(node)
229
+ emit_code(node.case_keyword_loc)
230
+ emit_whitespace
231
+ emit_code(node.predicate)
232
+ node.conditions.each { |c| emit_newline; emit_code(c) }
233
+ emit_newline if node.else_clause
234
+ emit_code(node.else_clause)
235
+ emit_newline
236
+ emit_code(node.end_keyword_loc)
237
+ end
238
+
239
+ def visit_case_match_node(node)
240
+ emit_code(node.case_keyword_loc)
241
+ emit_whitespace
242
+ emit_code(node.predicate)
243
+ emit_newline
244
+ visit_comma_separated_nodes(node.conditions)
245
+ emit_newline
246
+ emit_code(node.end_keyword_loc)
247
+ end
248
+
249
+ def visit_def_node(node)
250
+ emit_code(node.def_keyword_loc)
251
+ emit_whitespace
252
+ emit_code(node.receiver)
253
+ emit_code(node.operator_loc)
254
+ emit_code(node.name_loc)
255
+ emit_whitespace if !node.lparen_loc
256
+ emit_code(node.lparen_loc)
257
+ emit_code(node.parameters)
258
+ emit_code(node.rparen_loc)
259
+ emit_code(node.equal_loc)
260
+ emit_newline
261
+ emit_code(node.body)
262
+ emit_newline
263
+ emit_code(node.end_keyword_loc)
264
+ end
265
+
266
+ def visit_find_pattern_node(node)
267
+ emit_code(node.constant)
268
+ emit_code(node.opening_loc)
269
+ emit_code(node.left)
270
+ comma = node.left
271
+ comma = visit_comma_separated_nodes(node.requireds, comma)
272
+ if node.right
273
+ emit_comma if comma
274
+ emit_code(node.right)
275
+ end
276
+ emit_code(node.closing_loc)
277
+ end
278
+
279
+ def visit_hash_node(node)
280
+ emit_code(node.opening_loc)
281
+ visit_comma_separated_nodes(node.elements)
282
+ emit_code(node.closing_loc)
283
+ end
284
+
285
+ def visit_hash_pattern_node(node)
286
+ emit_code(node.constant)
287
+ emit_code(node.opening_loc)
288
+ visit_comma_separated_nodes(node.elements)
289
+ emit_code(node.closing_loc)
290
+ end
291
+
292
+ def visit_if_node(node)
293
+ return visit_if_node_ternary(node) if !node.if_keyword_loc
294
+ return visit_if_node_guard(node) if !node.end_keyword_loc
295
+
296
+ emit_code(node.if_keyword_loc)
297
+ emit_whitespace
298
+ emit_code(node.predicate)
299
+ emit_newline
300
+ emit_code(node.then_keyword_loc)
301
+ emit_newline
302
+ emit_code(node.statements)
303
+ emit_newline
304
+ emit_code(node.subsequent) if node.subsequent
305
+ emit_newline
306
+ emit_code(node.end_keyword_loc) if node.if_keyword_loc.slice == 'if'
307
+ end
308
+
309
+ def visit_if_node_guard(node)
310
+ emit_code(node.statements)
311
+ emit_whitespace
312
+ emit_code(node.if_keyword_loc)
313
+ emit_whitespace
314
+ emit_code(node.predicate)
315
+ end
316
+
317
+ def visit_if_node_ternary(node)
318
+ emit_code(node.predicate)
319
+ emit_whitespace
320
+ emit_code(node.then_keyword_loc)
321
+ emit_newline
322
+ emit_code(node.statements)
323
+ emit_newline
324
+ emit_code(node.subsequent)
325
+ end
326
+
327
+ def visit_interpolated_symbol_node(node)
328
+ heredoc = node.opening_loc && node.opening_loc.slice =~ /\<\</
329
+ emit_code(node.opening_loc)
330
+ emit_newline if heredoc
331
+ last_part = nil
332
+ node.parts.each { |p|
333
+ if heredoc && last_part && p.location.start_line != last_part.location.start_line
334
+ emit_newline
335
+ end
336
+ emit_code(p)
337
+ last_part = p
338
+ }
339
+ emit_newline if heredoc
340
+ emit_code(node.closing_loc)
341
+ end
342
+ alias_method :visit_interpolated_string_node, :visit_interpolated_symbol_node
343
+
344
+ def visit_index_operator_write_node(node)
345
+ emit_code(node.receiver)
346
+ emit_code(node.opening_loc)
347
+ emit_code(node.arguments)
348
+ if node.block
349
+ if !node.arguments.arguments.empty?
350
+ emit_comma
351
+ end
352
+ emit_code(node.block)
353
+ end
354
+ emit_code(node.closing_loc)
355
+ emit_whitespace
356
+ emit_code(node.binary_operator_loc)
357
+ emit_whitespace
358
+ emit_code(node.value)
359
+ end
360
+
361
+ def visit_index_or_write_node(node)
362
+ emit_code(node.receiver)
363
+ emit_code(node.opening_loc)
364
+ emit_code(node.arguments)
365
+ if node.block
366
+ if !node.arguments.arguments.empty?
367
+ emit_comma
368
+ end
369
+ emit_code(node.block)
370
+ end
371
+ emit_code(node.closing_loc)
372
+ emit_whitespace
373
+ emit_code(node.operator_loc)
374
+ emit_whitespace
375
+ emit_code(node.value)
376
+ end
377
+ alias_method :visit_index_and_write_node, :visit_index_or_write_node
378
+
379
+ def visit_interpolated_regular_expression_node(node)
380
+ emit_code(node.opening_loc)
381
+ node.parts.each { |p| emit_code(p) }
382
+ emit_code(node.closing_loc)
383
+ end
384
+
385
+ def visit_interpolated_x_string_node(node)
386
+ emit_code(node.opening_loc)
387
+ node.parts.each { |p| emit_code(p) }
388
+ emit_code(node.closing_loc)
389
+ end
390
+
391
+ def visit_keyword_hash_node(node)
392
+ visit_comma_separated_nodes(node.elements)
393
+ end
394
+
395
+ def visit_multi_target_node(node)
396
+ emit_code(node.lparen_loc)
397
+ comma = visit_comma_separated_nodes(node.lefts)
398
+ if node.rest
399
+ emit_comma if comma
400
+ emit_code(node.rest)
401
+ comma = true
402
+ end
403
+ visit_comma_separated_nodes(node.rights, comma)
404
+ emit_code(node.rparen_loc)
405
+ end
406
+
407
+ def visit_multi_write_node(node)
408
+ emit_code(node.lparen_loc)
409
+ comma = visit_comma_separated_nodes(node.lefts)
410
+ if node.rest
411
+ emit_comma if comma
412
+ emit_code(node.rest)
413
+ comma = true
414
+ end
415
+ visit_comma_separated_nodes(node.rights, comma)
416
+ emit_code(node.rparen_loc)
417
+ emit_whitespace
418
+ emit_code(node.operator_loc)
419
+ emit_whitespace
420
+ emit_code(node.value)
421
+ end
422
+
423
+ def visit_parameters_node(node)
424
+ comma = false
425
+ comma = visit_comma_separated_nodes(node.requireds, comma)
426
+ comma = visit_comma_separated_nodes(node.optionals, comma)
427
+ if node.rest
428
+ emit_comma if comma
429
+ emit_code(node.rest)
430
+ comma = true
431
+ end
432
+
433
+ comma = visit_comma_separated_nodes(node.posts, comma)
434
+ comma = visit_comma_separated_nodes(node.keywords, comma)
435
+
436
+ if node.keyword_rest
437
+ emit_comma if comma
438
+ comma = true
439
+ emit_code(node.keyword_rest)
440
+ end
441
+ if node.block
442
+ emit_comma if comma
443
+ comma = true
444
+ emit_code(node.block)
445
+ end
446
+ end
447
+
448
+ def visit_range_node(node)
449
+ emit_code(node.left)
450
+ emit_code(node.operator_loc)
451
+ emit_whitespace if node.right.is_a?(Ruby::Prism::RangeNode)
452
+ emit_code(node.right)
453
+ end
454
+
455
+ def visit_rescue_node(node)
456
+ emit_code(node.keyword_loc)
457
+ emit_whitespace
458
+ visit_comma_separated_nodes(node.exceptions)
459
+ emit_whitespace
460
+ emit_code(node.operator_loc)
461
+ emit_whitespace
462
+ emit_code(node.reference)
463
+ emit_newline
464
+ emit_code(node.statements)
465
+ emit_newline
466
+ emit_code(node.subsequent)
467
+ end
468
+
469
+ def visit_statements_node(node)
470
+ first = true
471
+ node.body&.each do |n|
472
+ emit_newline if !first
473
+ visit(n)
474
+ first = false
475
+ end
476
+ end
477
+
478
+ def visit_string_node(node)
479
+ heredoc = node.opening_loc && node.opening_loc.slice =~ /\<\</
480
+ emit_code(node.opening_loc)
481
+ emit_newline if heredoc
482
+ emit_code(node.content_loc)
483
+ emit_newline if heredoc
484
+ emit_code(node.closing_loc)
485
+ end
486
+
487
+ def visit_super_node(node)
488
+ emit_code(node.keyword_loc)
489
+ emit_code(node.lparen_loc)
490
+ emit_whitespace if !node.lparen_loc
491
+ emit_code(node.arguments)
492
+ emit_block_pre_rparen = node.block.is_a?(Ruby::Prism::BlockArgumentNode)
493
+ if emit_block_pre_rparen
494
+ emit_comma if node.arguments
495
+ emit_code(node.block)
496
+ end
497
+ emit_code(node.rparen_loc)
498
+ emit_code(node.block) if !emit_block_pre_rparen
499
+ end
500
+
501
+ def visit_undef_node(node)
502
+ emit_code(node.keyword_loc)
503
+ emit_whitespace
504
+ visit_comma_separated_nodes(node.names)
505
+ end
506
+
507
+ def visit_unless_node(node)
508
+ return visit_unless_node_guard(node) if !node.end_keyword_loc
509
+
510
+ emit_code(node.keyword_loc)
511
+ emit_whitespace
512
+ emit_code(node.predicate)
513
+ emit_code(node.then_keyword_loc)
514
+ emit_newline
515
+ emit_code(node.statements)
516
+ emit_newline
517
+ emit_code(node.else_clause) if node.else_clause
518
+ emit_newline
519
+ emit_code(node.end_keyword_loc) if node.keyword_loc.slice == 'unless'
520
+ end
521
+
522
+ def visit_unless_node_guard(node)
523
+ emit_code(node.statements)
524
+ emit_whitespace
525
+ emit_code(node.keyword_loc)
526
+ emit_whitespace
527
+ emit_code(node.predicate)
528
+ end
529
+
530
+ def visit_until_node(node)
531
+ return visit_until_node_guard(node) if !node.closing_loc
532
+
533
+ emit_code(node.keyword_loc)
534
+ emit_whitespace
535
+ emit_code(node.predicate)
536
+ emit_newline
537
+ emit_code(node.statements)
538
+ emit_newline
539
+ emit_code(node.closing_loc)
540
+ end
541
+
542
+ def visit_until_node_guard(node)
543
+ emit_code(node.statements)
544
+ emit_whitespace
545
+ emit_code(node.keyword_loc)
546
+ emit_whitespace
547
+ emit_code(node.predicate)
548
+ end
549
+
550
+ def visit_when_node(node)
551
+ emit_code(node.keyword_loc)
552
+ emit_whitespace
553
+ visit_comma_separated_nodes(node.conditions)
554
+ emit_newline
555
+ emit_code(node.statements)
556
+ end
557
+
558
+ def visit_while_node(node)
559
+ return visit_while_node_guard(node) if !node.closing_loc
560
+
561
+ emit_code(node.keyword_loc)
562
+ emit_whitespace
563
+ emit_code(node.predicate)
564
+ emit_newline
565
+ emit_code(node.statements)
566
+ emit_newline
567
+ emit_code(node.closing_loc)
568
+ end
569
+
570
+ def visit_while_node_guard(node)
571
+ emit_code(node.statements)
572
+ emit_whitespace
573
+ emit_code(node.keyword_loc)
574
+ emit_whitespace
575
+ emit_code(node.predicate)
576
+ end
577
+
578
+ private
579
+
580
+ def visit_plan(plan, node)
581
+ return send(plan, node) if plan.is_a?(Symbol)
582
+
583
+ plan.each_with_index do |sym, idx|
584
+ case sym
585
+ when :newline
586
+ emit_newline
587
+ when :whitespace
588
+ emit_whitespace
589
+ else
590
+ obj = node.send(sym)
591
+ emit_code(obj)
592
+ end
593
+ end
594
+ end
595
+
596
+ def visit_comma_separated_nodes(list, comma = false)
597
+ if list
598
+ list.each do |child|
599
+ emit_comma if comma
600
+ emit_code(child)
601
+ comma = true
602
+ end
603
+ end
604
+ comma
605
+ end
606
+
607
+ def visit_space_separated_nodes(list)
608
+ return if !list
609
+
610
+ first = true
611
+ list.each do |child|
612
+ emit_whitespace if !first
613
+ emit_code(child)
614
+ first = false
615
+ end
616
+ end
617
+
618
+ def emit_verbatim(node)
619
+ emit_code(node.location)
620
+ end
621
+
622
+ def emit_code(loc)
623
+ return if !loc
624
+ return visit(loc) if loc.is_a?(Ruby::Prism::Node)
625
+
626
+ emit(loc.slice.chomp)
627
+ end
628
+
629
+ def emit(str)
630
+ @buffer << str
631
+ end
632
+
633
+ def emit_newline
634
+ @buffer << "\n" if @buffer[-1] != "\n"
635
+ end
636
+
637
+ def emit_whitespace
638
+ @buffer << " "
639
+ end
640
+
641
+ def emit_comma
642
+ @buffer << ","
643
+ end
644
+
645
+ def emit_semicolon
646
+ @buffer << ';'
647
+ end
648
+
649
+ def emit_nothing(node)
650
+ end
651
+ end
652
+ end
@@ -0,0 +1,4 @@
1
+ module Steadfast
2
+ # Steadfast version
3
+ VERSION = '0.0.1'
4
+ end
data/lib/steadfast.rb ADDED
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'steadfast/sourcifier'
4
+
5
+ RubyVM.keep_script_lines = true
6
+
7
+ module Steadfast
8
+ class Error < StandardError
9
+ end
10
+
11
+ class << self
12
+ def to_ast(obj)
13
+ case obj
14
+ when Proc, Method, UnboundMethod
15
+ obj.syntax_tree
16
+ when Ruby::Prism::Node
17
+ obj
18
+ else
19
+ raise ArgumentError, "Invalid object type"
20
+ end
21
+ end
22
+
23
+ def to_source(obj, **)
24
+ obj = to_ast(obj)
25
+ Sourcifier.new(**).to_source(obj)
26
+ end
27
+
28
+ private
29
+
30
+ def proc_ast(proc)
31
+ fn, lineno = proc.source_location
32
+ pr = Prism.parse(get_source(fn), filepath: fn)
33
+ program = pr.value
34
+
35
+ Finder.find(program, proc) do
36
+ on(:lambda) do |node|
37
+ found!(node) if node.location.start_line == lineno
38
+ super(node)
39
+ end
40
+ on(:block) do |node|
41
+ found!(node) if node.location.start_line == lineno
42
+ super(node)
43
+ end
44
+ on(:call) do |node|
45
+ case node.name
46
+ when :proc, :lambda
47
+ found!(node) if node.block && node.block.location.start_line == lineno
48
+ end
49
+ super(node)
50
+ end
51
+ end
52
+ rescue Errno::ENOENT
53
+ raise Sirop::Error, "Could not get source for proc"
54
+ end
55
+
56
+ def method_ast(method)
57
+ fn, lineno = method.source_location
58
+ pr = Prism.parse(get_source(fn), filepath: fn)
59
+ program = pr.value
60
+
61
+ Finder.find(program, method) do
62
+ on(:def) do |node|
63
+ found!(node) if node.name == method.name && node.location.start_line == lineno
64
+ super(node)
65
+ end
66
+ end
67
+ end
68
+
69
+ def get_source(fn)
70
+ return get_irb_source if fn == '(irb)'
71
+
72
+ IO.read(fn)
73
+ end
74
+
75
+ def get_irb_source
76
+ io = IRB.CurrentContext.io
77
+ last_line = io.instance_variable_get(:@line_no)
78
+ (1..last_line).map { io.line(it) }.join
79
+ end
80
+ end
81
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: steadfast
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sharon Rosner
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: debug_inspector
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ -
17
+ - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: "0"
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ -
25
+ - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: "0"
28
+ - !ruby/object:Gem::Dependency
29
+ name: minitest
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ -
33
+ - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ -
41
+ - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ - !ruby/object:Gem::Dependency
45
+ name: irb
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ -
49
+ - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ type: :development
53
+ prerelease: false
54
+ version_requirements: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ -
57
+ - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ - !ruby/object:Gem::Dependency
61
+ name: syntax_tree
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ -
65
+ - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ -
73
+ - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ email: sharon@noteflakes.com
77
+ executables: []
78
+ extensions: []
79
+ extra_rdoc_files:
80
+ - README.md
81
+ files:
82
+ - CHANGELOG.md
83
+ - README.md
84
+ - lib/steadfast.rb
85
+ - lib/steadfast/sourcifier.rb
86
+ - lib/steadfast/version.rb
87
+ homepage: "http://github.com/digital-fabric/steadfast"
88
+ licenses:
89
+ - MIT
90
+ metadata:
91
+ homepage_uri: "https://github.com/digital-fabric/steadfast"
92
+ documentation_uri: "https://www.rubydoc.info/gems/steadfast"
93
+ changelog_uri: "https://github.com/digital-fabric/steadfast/blob/main/CHANGELOG.md"
94
+ rdoc_options:
95
+ - "--title"
96
+ - Steadfast
97
+ - "--main"
98
+ - README.md
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ -
104
+ - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: "4.0"
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ -
110
+ - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: "0"
113
+ requirements: []
114
+ rubygems_version: 4.1.0.beta2
115
+ specification_version: 4
116
+ summary: "Steadfast: modern metaprogramming for Ruby"
117
+ test_files: []