-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathscratch_ruby.py
More file actions
120 lines (105 loc) · 3.29 KB
/
Copy pathscratch_ruby.py
File metadata and controls
120 lines (105 loc) · 3.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import re
name_pat = r"(?:[^\W\d]\w*[=!?]?|\[\]=?|<<|>>|<=>|===?|!=|=~|!~|<=?|>=?|[+\-*/%&|^~`!])"
prefix_pat = r"(?:(?:[^\W\d]\w*(?:::[^\W\d]\w*)*\.|self\.)[ \t\n]*)?"
func_start = re.compile(
r'^[ \t]*(?:def[ \t\n]+' + prefix_pat + r'|define_method[ \t\n]*\(?[ \t\n]*[:\'"]?)(' + name_pat + r')(?=[ \t\n]*[)(]|[\'"]?[ \t\n]*(?:\{|do)|[ \t\n]|$)',
re.M,
)
funcs = [
"def foo",
"def self.class_method",
"def Module::NestedClass.method",
"def method_name?",
"def destructive_method!",
"def []",
"def []=(key, val)",
"def <<(item)",
"def ==(other)",
"def =~(other)",
"def `(cmd)",
"def foo = 'one-liner'",
"def foo() = 42",
"def \n crazy_whitespace \n ()",
"def\ttabbed_method",
"def ಠ_ಠ",
]
print("FUNC_START VALID")
for t in funcs:
m = func_start.search(t)
print(f"{t!r} -> {m.group(1) if m else None}")
class_start = re.compile(
r"^[ \t]*(?:class|module)\s+([^\W\d]\w*(?:::[^\W\d]\w*)*|<<\s*self|<<\s*@[a-zA-Z_]\w*)(?:\s*<\s*([^\W\d]\w*(?:::[^\W\d]\w*)*))?",
re.M,
)
classes = [
"class Foo",
"class Foo < Bar",
"class Module::NestedClass",
"class << self",
"class << @object",
"class Foo<Bar",
"class \n Foo \n < \n Bar",
"class Foo; end",
"class ಠ_ಠ < Object",
]
print("\nCLASS_START VALID")
for t in classes:
m = class_start.search(t)
print(f"{t!r} -> {m.group(1) if m else None}")
TOKEN = r"(?:[^()\'\"]|'(?:[^'\\]|\\.)*'|\"(?:[^\"\\]|\\.)*\")"
args_pat = r"\bdef\s+" + prefix_pat + name_pat + r"\s*\(((?:" + TOKEN + r"|\((?:" + TOKEN + r"|\(" + TOKEN + r"*\))*\))*)\)|\bdo\s*\|([^|]*)\||\{\s*\|([^|]*)\||->\s*\(((?:" + TOKEN + r"|\((?:" + TOKEN + r"|\(" + TOKEN + r"*\))*\))*)\)"
args = re.compile(args_pat, re.M)
arg_cases = [
"def foo()",
"def foo(a)",
"def foo(a, b)",
"def foo(a = 1, b = 'str')",
"def foo(*args)",
"def foo(**kwargs)",
"def foo(&block)",
"def foo(a, b=1, *c, d:, e: 2, **f, &g)",
"def foo(...)",
"def foo(*, **)",
"def foo(a, \n b)",
"def foo(a = { x: 1, y: 'foo(bar)' })",
"def foo(a = lambda { |x| x.foo })",
"def foo(a = %w[one two three])",
"def foo(a: 1, b: 'def foo')",
'def foo(a="\\\"")',
'def foo(a = ")", b = 2)',
"def foo(a = <<-EOF\n multiline\nEOF\n)",
]
print("\nARGS VALID")
for t in arg_cases:
m = args.search(t)
# The groups will be:
# 1: def args
# 2: do block args
# 3: brace block args
# 4: lambda args
res = m.group(1) or m.group(2) or m.group(3) or m.group(4) if m else None
print(f"{t!r} -> {res}")
dependency_capture = re.compile(
r"\b(?:require|require_relative|load|autoload)\b[ \t\n(]*(?:['\"]([^'\"]+)['\"]|%[qQwW]\W([^ \t\n\W]+)\W)|\b(?:include|extend)\b[ \t\n(]+([^\W\d]\w*(?:::[^\W\d]\w*)*)",
re.M,
)
deps = [
"require 'json'",
'require "net/http"',
"require_relative 'my_lib'",
"include Enumerable",
"extend ActiveSupport::Concern",
"require('openssl')",
"require %q(foo)",
"require %Q{bar}",
"require %W[baz].first",
"require \n 'foo'",
"include(Foo::Bar)",
"require 'foo' # comment",
'require_relative"foo"',
]
print("\nDEPENDENCY_CAPTURE VALID")
for t in deps:
m = dependency_capture.search(t)
res = m.group(1) or m.group(2) or m.group(3) if m else None
print(f"{t!r} -> {res}")