at_coder_friends 0.5.2 → 0.6.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +1 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/config/default.yml +3 -0
- data/docs/CONFIGURATION.md +74 -9
- data/lib/at_coder_friends.rb +10 -5
- data/lib/at_coder_friends/cli.rb +2 -5
- data/lib/at_coder_friends/context.rb +4 -0
- data/lib/at_coder_friends/emitter.rb +2 -2
- data/lib/at_coder_friends/generator/cxx_builtin.rb +191 -0
- data/lib/at_coder_friends/generator/main.rb +53 -0
- data/lib/at_coder_friends/generator/ruby_builtin.rb +128 -0
- data/lib/at_coder_friends/parser/binary.rb +39 -0
- data/lib/at_coder_friends/parser/constraints.rb +36 -0
- data/lib/at_coder_friends/parser/{format_parser.rb → input_format.rb} +42 -30
- data/lib/at_coder_friends/parser/interactive.rb +29 -0
- data/lib/at_coder_friends/parser/main.rb +6 -3
- data/lib/at_coder_friends/parser/sample_data.rb +24 -0
- data/lib/at_coder_friends/parser/section_wrapper.rb +49 -0
- data/lib/at_coder_friends/parser/{page_parser.rb → sections.rb} +44 -50
- data/lib/at_coder_friends/problem.rb +40 -24
- data/lib/at_coder_friends/scraping/agent.rb +1 -5
- data/lib/at_coder_friends/scraping/authentication.rb +2 -2
- data/lib/at_coder_friends/scraping/session.rb +1 -1
- data/lib/at_coder_friends/scraping/tasks.rb +2 -6
- data/lib/at_coder_friends/test_runner/base.rb +36 -31
- data/lib/at_coder_friends/test_runner/judge.rb +2 -6
- data/lib/at_coder_friends/test_runner/sample.rb +8 -6
- data/lib/at_coder_friends/version.rb +1 -1
- data/tasks/regression/check_diff.rake +29 -0
- data/tasks/regression/check_parse.rake +56 -0
- data/tasks/regression/regression.rb +67 -0
- data/tasks/regression/section_list.rake +41 -0
- data/tasks/regression/setup.rake +48 -0
- data/templates/cxx_builtin_default.cxx +26 -0
- data/templates/cxx_builtin_interactive.cxx +61 -0
- data/templates/ruby_builtin_default.rb +5 -0
- data/templates/ruby_builtin_interactive.rb +32 -0
- metadata +21 -8
- data/lib/at_coder_friends/cxx_generator.rb +0 -169
- data/lib/at_coder_friends/parser/constraints_parser.rb +0 -26
- data/lib/at_coder_friends/ruby_generator.rb +0 -97
- data/tasks/regression.rake +0 -163
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cfcd8c547d0916bc1db5c76b5c698e410ddb3541
|
4
|
+
data.tar.gz: 03e06ac13b32c2d55166cf1cefa83ad9cfbe3fa2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d73018f888e13b7a37a19b8945b879513caf4952ff5bdcbe9412c413a84d05c4c055fb00166e97fe3b5420637b0c87c767796f9c2835e82c879451f854b2fc9d
|
7
|
+
data.tar.gz: 602aa37bf5f315a9bccfbd3e676f19b0651c4687fbe351e65d670359c956b74e230ebcedade158928cd1486092947af1bfb34c1504632b7389d1029da2afb70a
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
data/config/default.yml
CHANGED
data/docs/CONFIGURATION.md
CHANGED
@@ -20,9 +20,14 @@ Its contents are merged with the default settings.
|
|
20
20
|
```.at_coder_friends.yml```で新しい設定項目の追加や、既存の設定項目の変更をすると、
|
21
21
|
その内容がデフォルト設定にマージされます。
|
22
22
|
|
23
|
+
[/config/default.yml](/config/default.yml)
|
24
|
+
|
23
25
|
```YAML
|
24
26
|
user: ~
|
25
27
|
password: ~
|
28
|
+
generators:
|
29
|
+
- RubyBuiltin
|
30
|
+
- CxxBuiltin
|
26
31
|
ext_settings:
|
27
32
|
'awk':
|
28
33
|
submit_lang: '3506' # Awk (mawk)
|
@@ -115,15 +120,43 @@ ext_settings:
|
|
115
120
|
|
116
121
|
- user
|
117
122
|
AtCoder username
|
118
|
-
If omitted, you are prompted to enter it on the first
|
123
|
+
If omitted, you are prompted to enter it on the first login.
|
119
124
|
AtCoderのユーザ名
|
120
|
-
|
125
|
+
省略した場合、初回のログイン時に入力を求められます
|
121
126
|
|
122
127
|
- password
|
123
128
|
AtCoder password
|
124
|
-
If omitted, you are prompted to enter it on the first
|
129
|
+
If omitted, you are prompted to enter it on the first login.
|
125
130
|
AtCoderのパスワード
|
126
|
-
|
131
|
+
省略した場合、初回のログイン時に入力を求められます
|
132
|
+
|
133
|
+
- generators
|
134
|
+
List of source generator class names
|
135
|
+
In default, ```RubyBuiltin``` and ```CxxBuiltin``` are available.
|
136
|
+
For those other than above, corresponding plugin will be used
|
137
|
+
if it has been installed (?).
|
138
|
+
ソーススケルトンを生成するジェネレータのクラス名(リスト形式)
|
139
|
+
既定の状態では「RubyBuiltin」と「CxxBuiltin」が利用でき、
|
140
|
+
上記以外については、対応するプラグインがインストールされていれば
|
141
|
+
それが使われます(?)
|
142
|
+
|
143
|
+
For example, if ```RubyAlt``` is specified as generator name,
|
144
|
+
following plugin will be used:
|
145
|
+
|
146
|
+
| | |
|
147
|
+
|-----------------|---------------------------------------------------|
|
148
|
+
|Gem Name |```at_coder_friends-generator-ruby_alt``` |
|
149
|
+
|Require Statement|```require 'at_coder_friends/generator/ruby_alt'```|
|
150
|
+
|Main Class Name |```AtCoderFriends::Generator::RubyAlt``` |
|
151
|
+
|
152
|
+
- generator_settings
|
153
|
+
- _(generator name)_
|
154
|
+
Settings for each generator
|
155
|
+
For details, see the manual of each generator.
|
156
|
+
[Settings for built-in generators](#builtin-generator-settings)
|
157
|
+
ジェネレータ毎の設定
|
158
|
+
詳細は各ジェネレータのマニュアルを参照してください
|
159
|
+
[組込ジェネレータの設定](#builtin-generator-settings)
|
127
160
|
|
128
161
|
- ext_settings
|
129
162
|
Extension specific settings
|
@@ -159,10 +192,23 @@ ext_settings:
|
|
159
192
|
Execution command for Linux(optional)
|
160
193
|
Linux専用実行コマンド(あれば設定)
|
161
194
|
|
195
|
+
<a id="builtin-generator-settings"></a>
|
196
|
+
### Settings for RubyBuiltin generator
|
197
|
+
| Setting | Description | Default |
|
198
|
+
|---------|-------------|---------|
|
199
|
+
|default_template|Source template file path|[/templates/ruby_builtin_default.rb](/templates/ruby_builtin_default.rb)|
|
200
|
+
|interactive_template|Source template file path for interactive problems|[/templates/ruby_builtin_interactive.rb](/templates/ruby_builtin_interactive.rb)|
|
201
|
+
|
202
|
+
### Settings for CxxBuiltin generator
|
203
|
+
| Setting | Description | Default |
|
204
|
+
|---------|-------------|---------|
|
205
|
+
|default_template|Source template file path|[/templates/cxx_builtin_default.cxx](/templates/cxx_builtin_default.cxx)|
|
206
|
+
|interactive_template|Source template file path for interactive problems|[/templates/cxx_builtin_interactive.cxx](/templates/cxx_builtin_interactive.cxx)|
|
207
|
+
|
162
208
|
### Language ID list (2019/09/16)
|
163
209
|
|
164
210
|
| Language ID | Description |
|
165
|
-
|
211
|
+
|-------------|-------------|
|
166
212
|
|3003|C++14 (GCC 5.4.1)|
|
167
213
|
|3001|Bash (GNU bash v4.3.11)|
|
168
214
|
|3002|C (GCC 5.4.1)|
|
@@ -229,13 +275,32 @@ during execution.
|
|
229
275
|
test_cmd 文字列中の以下の変数には、
|
230
276
|
実行時に対象ファイルのパス情報が展開されます。
|
231
277
|
|
232
|
-
| Variable | Descripton
|
233
|
-
|
234
|
-
|{dir}|the directory name of the file<br>ファイルの置かれているディレクトリ名|
|
235
|
-
|{base}|the file name excluding the extension<br
|
278
|
+
| Variable | Descripton |
|
279
|
+
|----------|----------------------------------------------------------------|
|
280
|
+
|{dir} |the directory name of the file<br>ファイルの置かれているディレクトリ名|
|
281
|
+
|{base} |the file name excluding the extension<br>拡張子を除いたファイル名 |
|
236
282
|
|
237
283
|
## Examples of overwriting settings
|
238
284
|
|
285
|
+
- Use only Ruby source generator
|
286
|
+
Rubyのソースジェネレータのみ使用
|
287
|
+
```YAML
|
288
|
+
generators:
|
289
|
+
- RubyBuiltin
|
290
|
+
```
|
291
|
+
- Do not use source generator
|
292
|
+
ソースジェネレータを使用しない
|
293
|
+
```YAML
|
294
|
+
generators: ~
|
295
|
+
```
|
296
|
+
- change C++ source template
|
297
|
+
C++のソース雛型を変更
|
298
|
+
```YAML
|
299
|
+
generator_settings:
|
300
|
+
CxxBuiltin:
|
301
|
+
default_template: /path/to/template
|
302
|
+
```
|
303
|
+
|
239
304
|
- Test .py with local Python
|
240
305
|
.pyをローカル環境のPythonでテスト
|
241
306
|
```YAML
|
data/lib/at_coder_friends.rb
CHANGED
@@ -15,12 +15,17 @@ require 'at_coder_friends/scraping/custom_test'
|
|
15
15
|
require 'at_coder_friends/scraping/submission'
|
16
16
|
require 'at_coder_friends/scraping/tasks'
|
17
17
|
require 'at_coder_friends/scraping/agent'
|
18
|
-
require 'at_coder_friends/parser/
|
19
|
-
require 'at_coder_friends/parser/
|
20
|
-
require 'at_coder_friends/parser/
|
18
|
+
require 'at_coder_friends/parser/section_wrapper'
|
19
|
+
require 'at_coder_friends/parser/sections'
|
20
|
+
require 'at_coder_friends/parser/sample_data'
|
21
|
+
require 'at_coder_friends/parser/input_format'
|
22
|
+
require 'at_coder_friends/parser/constraints'
|
23
|
+
require 'at_coder_friends/parser/interactive'
|
24
|
+
require 'at_coder_friends/parser/binary'
|
21
25
|
require 'at_coder_friends/parser/main'
|
22
|
-
require 'at_coder_friends/
|
23
|
-
require 'at_coder_friends/
|
26
|
+
require 'at_coder_friends/generator/cxx_builtin'
|
27
|
+
require 'at_coder_friends/generator/ruby_builtin'
|
28
|
+
require 'at_coder_friends/generator/main'
|
24
29
|
require 'at_coder_friends/emitter'
|
25
30
|
require 'at_coder_friends/context'
|
26
31
|
require 'at_coder_friends/cli'
|
data/lib/at_coder_friends/cli.rb
CHANGED
@@ -92,17 +92,14 @@ module AtCoderFriends
|
|
92
92
|
raise AppError, "#{path} is not empty." \
|
93
93
|
if Dir.exist?(path) && !Dir["#{path}/*"].empty?
|
94
94
|
|
95
|
-
rb_gen = RubyGenerator.new
|
96
|
-
cxx_gen = CxxGenerator.new
|
97
95
|
ctx.scraping_agent.fetch_all do |pbm|
|
98
96
|
Parser::Main.process(pbm)
|
99
|
-
|
100
|
-
cxx_gen.process(pbm)
|
97
|
+
ctx.generator.process(pbm)
|
101
98
|
ctx.emitter.emit(pbm)
|
102
99
|
end
|
103
100
|
end
|
104
101
|
|
105
|
-
def test_one(id =
|
102
|
+
def test_one(id = '001')
|
106
103
|
ctx.sample_test_runner.test_one(id)
|
107
104
|
end
|
108
105
|
|
@@ -14,8 +14,8 @@ module AtCoderFriends
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def emit(pbm)
|
17
|
-
pbm.
|
18
|
-
pbm.
|
17
|
+
pbm.samples.each { |smp| emit_sample(pbm, smp) }
|
18
|
+
pbm.sources.each { |src| emit_source(pbm, src) }
|
19
19
|
end
|
20
20
|
|
21
21
|
def emit_sample(pbm, smp)
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Generator
|
5
|
+
module CxxBuiltinConstants
|
6
|
+
ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
|
7
|
+
TMPL_DIR = File.join(ACF_HOME, 'templates')
|
8
|
+
DEFAULT_TMPL = File.join(TMPL_DIR, 'cxx_builtin_default.cxx')
|
9
|
+
INTERACTIVE_TMPL = File.join(TMPL_DIR, 'cxx_builtin_interactive.cxx')
|
10
|
+
SCANF_FMTS = [
|
11
|
+
'scanf("%<fmt>s", %<addr>s);',
|
12
|
+
'REP(i, %<sz1>s) scanf("%<fmt>s", %<addr>s);',
|
13
|
+
'REP(i, %<sz1>s) REP(j, %<sz2>s) scanf("%<fmt>s", %<addr>s);'
|
14
|
+
].freeze
|
15
|
+
FMT_FMTS = { number: '%d', string: '%s', char: '%s' }.freeze
|
16
|
+
ADDR_FMTS = {
|
17
|
+
single: {
|
18
|
+
number: '&%<v>s',
|
19
|
+
string: '%<v>s'
|
20
|
+
},
|
21
|
+
harray: {
|
22
|
+
number: '%<v>s + i',
|
23
|
+
string: '%<v>s[i]',
|
24
|
+
char: '%<v>s'
|
25
|
+
},
|
26
|
+
varray: {
|
27
|
+
number: '%<v>s + i',
|
28
|
+
string: '%<v>s[i]'
|
29
|
+
},
|
30
|
+
matrix: {
|
31
|
+
number: '&%<v>s[i][j]',
|
32
|
+
string: '%<v>s[i][j]',
|
33
|
+
char: '%<v>s[i]'
|
34
|
+
}
|
35
|
+
}.freeze
|
36
|
+
DEFAULT_OUTPUT = <<~TEXT
|
37
|
+
int ans = 0;
|
38
|
+
printf("%d\\n", ans);
|
39
|
+
TEXT
|
40
|
+
BINARY_OUTPUT_FMT = <<~TEXT
|
41
|
+
bool cond = false;
|
42
|
+
puts(cond ? "%s" : "%s");
|
43
|
+
TEXT
|
44
|
+
end
|
45
|
+
|
46
|
+
# generates C++ source code from problem description
|
47
|
+
class CxxBuiltin
|
48
|
+
include CxxBuiltinConstants
|
49
|
+
|
50
|
+
attr_reader :cfg, :pbm
|
51
|
+
|
52
|
+
def initialize(cfg = nil)
|
53
|
+
@cfg = cfg || {}
|
54
|
+
end
|
55
|
+
|
56
|
+
def process(pbm)
|
57
|
+
src = generate(pbm)
|
58
|
+
pbm.add_src(:cxx, src)
|
59
|
+
end
|
60
|
+
|
61
|
+
def generate(pbm)
|
62
|
+
@pbm = pbm
|
63
|
+
src = File.read(select_template)
|
64
|
+
src = embed_lines(src, '/*** URL ***/', [pbm.url])
|
65
|
+
src = embed_lines(src, '/*** CONSTS ***/', gen_consts)
|
66
|
+
src = embed_lines(src, '/*** DCLS ***/', gen_decls)
|
67
|
+
src = embed_lines(src, '/*** INPUTS ***/', gen_inputs)
|
68
|
+
embed_lines(src, '/*** OUTPUT ***/', gen_output.split("\n"))
|
69
|
+
end
|
70
|
+
|
71
|
+
def embed_lines(src, pat, lines)
|
72
|
+
re = Regexp.escape(pat)
|
73
|
+
src.gsub(
|
74
|
+
/^(.*)#{re}(.*)$/,
|
75
|
+
lines.map { |s| '\1' + s + '\2' }.join("\n")
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def select_template(interactive = pbm.options.interactive)
|
80
|
+
interactive ? interactive_template : default_template
|
81
|
+
end
|
82
|
+
|
83
|
+
def default_template
|
84
|
+
cfg['default_template'] || DEFAULT_TMPL
|
85
|
+
end
|
86
|
+
|
87
|
+
def interactive_template
|
88
|
+
cfg['interactive_template'] || INTERACTIVE_TMPL
|
89
|
+
end
|
90
|
+
|
91
|
+
def gen_consts(constraints = pbm.constraints)
|
92
|
+
constraints
|
93
|
+
.select { |c| c.type == :max }
|
94
|
+
.map { |c| "const int #{c.name.upcase}_MAX = #{c.value};" }
|
95
|
+
end
|
96
|
+
|
97
|
+
def gen_decls(inpdefs = pbm.formats)
|
98
|
+
inpdefs.map { |inpdef| gen_decl(inpdef) }.flatten
|
99
|
+
end
|
100
|
+
|
101
|
+
def gen_decl(inpdef)
|
102
|
+
case inpdef.container
|
103
|
+
when :single
|
104
|
+
gen_single_decl(inpdef)
|
105
|
+
when :harray
|
106
|
+
gen_harray_decl(inpdef)
|
107
|
+
when :varray
|
108
|
+
gen_varray_decl(inpdef)
|
109
|
+
when :matrix
|
110
|
+
gen_matrix_decl(inpdef)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def gen_single_decl(inpdef)
|
115
|
+
names = inpdef.names
|
116
|
+
case inpdef.item
|
117
|
+
when :number
|
118
|
+
dcl = names.join(', ')
|
119
|
+
"int #{dcl};"
|
120
|
+
when :string
|
121
|
+
names.map { |v| "char #{v}[#{v.upcase}_MAX + 1];" }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def gen_harray_decl(inpdef)
|
126
|
+
v = inpdef.names[0]
|
127
|
+
sz = gen_arr_size(inpdef.size)[0]
|
128
|
+
case inpdef.item
|
129
|
+
when :number
|
130
|
+
"int #{v}[#{sz}];"
|
131
|
+
when :string
|
132
|
+
"char #{v}[#{sz}][#{v.upcase}_MAX + 1];"
|
133
|
+
when :char
|
134
|
+
"char #{v}[#{sz} + 1];"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def gen_varray_decl(inpdef)
|
139
|
+
names = inpdef.names
|
140
|
+
sz = gen_arr_size(inpdef.size)[0]
|
141
|
+
case inpdef.item
|
142
|
+
when :number
|
143
|
+
names.map { |v| "int #{v}[#{sz}];" }
|
144
|
+
when :string
|
145
|
+
names.map { |v| "char #{v}[#{sz}][#{v.upcase}_MAX + 1];" }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def gen_matrix_decl(inpdef)
|
150
|
+
v = inpdef.names[0]
|
151
|
+
sz1, sz2 = gen_arr_size(inpdef.size)
|
152
|
+
case inpdef.item
|
153
|
+
when :number
|
154
|
+
"int #{v}[#{sz1}][#{sz2}];"
|
155
|
+
when :string
|
156
|
+
"char #{v}[#{sz1}][#{sz2}][#{v.upcase}_MAX + 1];"
|
157
|
+
when :char
|
158
|
+
"char #{v}[#{sz1}][#{sz2} + 1];"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def gen_arr_size(szs)
|
163
|
+
szs.map { |sz| sz =~ /\D/ ? "#{sz.upcase}_MAX" : sz }
|
164
|
+
end
|
165
|
+
|
166
|
+
def gen_inputs(inpdefs = pbm.formats)
|
167
|
+
inpdefs.map { |inpdef| gen_input(inpdef) }.flatten
|
168
|
+
end
|
169
|
+
|
170
|
+
# rubocop:disable Metrics/AbcSize
|
171
|
+
def gen_input(inpdef)
|
172
|
+
dim = inpdef.size.size - (inpdef.item == :char ? 1 : 0)
|
173
|
+
scanf = SCANF_FMTS[dim]
|
174
|
+
sz1, sz2 = inpdef.size
|
175
|
+
fmt = FMT_FMTS[inpdef.item] * inpdef.names.size
|
176
|
+
addr_fmt = ADDR_FMTS[inpdef.container][inpdef.item]
|
177
|
+
addr = inpdef.names.map { |v| format(addr_fmt, v: v) }.join(', ')
|
178
|
+
format(scanf, sz1: sz1, sz2: sz2, fmt: fmt, addr: addr)
|
179
|
+
end
|
180
|
+
# rubocop:enable Metrics/AbcSize
|
181
|
+
|
182
|
+
def gen_output(vs = pbm.options.binary_values)
|
183
|
+
if vs
|
184
|
+
format(BINARY_OUTPUT_FMT, *vs)
|
185
|
+
else
|
186
|
+
DEFAULT_OUTPUT
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Generator
|
5
|
+
# entry point of source generation
|
6
|
+
class Main
|
7
|
+
attr_reader :ctx
|
8
|
+
|
9
|
+
def initialize(ctx)
|
10
|
+
@ctx = ctx
|
11
|
+
@cache = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def process(pbm)
|
15
|
+
generators = ctx.config.dig('generators') || []
|
16
|
+
generators.each do |gen_name|
|
17
|
+
gen_obj = load_obj(gen_name)
|
18
|
+
gen_obj.process(pbm)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_obj(gen_name)
|
23
|
+
@cache[gen_name] ||= begin
|
24
|
+
gen_class = load_class(gen_name)
|
25
|
+
gen_cnf = config_for(gen_name)
|
26
|
+
gen_class.new(gen_cnf)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_class(gen_name)
|
31
|
+
unless AtCoderFriends::Generator.const_defined?(gen_name)
|
32
|
+
require "at_coder_friends/generator/#{to_snake(gen_name)}"
|
33
|
+
end
|
34
|
+
AtCoderFriends::Generator.const_get(gen_name)
|
35
|
+
rescue LoadError
|
36
|
+
raise AppError, "plugin load error : generator #{gen_name} not found."
|
37
|
+
end
|
38
|
+
|
39
|
+
def config_for(gen_name)
|
40
|
+
ctx.config.dig('generator_settings', gen_name) || {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_snake(str)
|
44
|
+
str
|
45
|
+
.gsub(/::/, '/')
|
46
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
47
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
48
|
+
.tr('-', '_')
|
49
|
+
.downcase
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|