Skip to content

Commit d6ea4a9

Browse files
feat: generate RuboCop-clean Ruby SDK files
Emit Layout-clean Ruby from Twig templates so a fresh generation already matches RuboCop 1.90.0 (4-space indent, Layout cops only). Ship .rubocop.yml with the SDK and fail CI on formatter regressions. Co-authored-by: Chirag Aggarwal <chiragaggarwal5k@gmail.com>
1 parent 63c5407 commit d6ea4a9

22 files changed

Lines changed: 182 additions & 94 deletions

.github/workflows/validation.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jobs:
1414
FLUTTER_VERSION: '3.35.7'
1515
KTLINT_VERSION: '1.8.0'
1616
KTLINT_SHA256: 'a3fd620207d5c40da6ca789b95e7f823c54e854b7fade7f613e91096a3706d75'
17+
RUBOCOP_VERSION: '1.90.0'
1718
OSV_SCANNER_VERSION: 'v2.3.1'
1819
strategy:
1920
fail-fast: false
@@ -301,6 +302,13 @@ jobs:
301302
gem install bundler-audit
302303
bundle-audit update
303304
bundle-audit check --no-update
305+
if [ ! -f .rubocop.yml ]; then
306+
echo "Generated SDK is missing .rubocop.yml"
307+
exit 1
308+
fi
309+
gem install rubocop --version "${RUBOCOP_VERSION}"
310+
rubocop --version
311+
rubocop
304312
;;
305313
node)
306314
npm ci

AGENTS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ When you change templates for a language below, regenerate and run that language
120120
(cd examples/python && python -m black --check setup.py)
121121
```
122122

123+
- **Ruby** — RuboCop 1.90.0 (Layout cops, 4-space indent)
124+
125+
```bash
126+
rm -rf examples/ruby
127+
php example.php ruby server
128+
(cd examples/ruby && rubocop)
129+
```
130+
123131
- **PHP** — Pint, PHPStan, and Rector
124132

125133
```bash

src/SDK/Language/Ruby.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ public function getFiles(): array
122122
'destination' => 'Gemfile',
123123
'template' => 'ruby/Gemfile.twig',
124124
],
125+
[
126+
'scope' => 'copy',
127+
'destination' => '.rubocop.yml',
128+
'template' => 'ruby/.rubocop.yml',
129+
],
125130
[
126131
'scope' => 'default',
127132
'destination' => '{{ spec.info.title | caseDash }}.gemspec',
@@ -383,6 +388,14 @@ public function getFilters(): array
383388
{
384389
return [
385390
new TwigFilter('caseEnumKey', fn(string $value): string => $this->toUpperSnakeCase($value)),
391+
new TwigFilter('rtrimLines', function (mixed $value): string {
392+
$lines = explode("\n", str_replace("\r\n", "\n", (string) $value));
393+
foreach ($lines as $index => $line) {
394+
$lines[$index] = rtrim($line);
395+
}
396+
397+
return implode("\n", $lines);
398+
}),
386399
new TwigFilter('enumExample', function (Schema|Parameter $param): string {
387400
$schema = $this->getSchema($param);
388401
$enumSchema = $this->getEnumSchema($param);

templates/ruby/.rubocop.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# RuboCop layout for the generated Ruby SDK.
2+
# IndentationWidth 4 matches the published sdk-for-ruby house style.
3+
# Only Layout cops are enabled: this is a formatter check, not a full linter.
4+
AllCops:
5+
NewCops: enable
6+
TargetRubyVersion: 2.7
7+
SuggestExtensions: false
8+
DisabledByDefault: true
9+
Exclude:
10+
- '.github/**/*'
11+
- 'vendor/**/*'
12+
- '.bundle/**/*'
13+
14+
Layout:
15+
Enabled: true
16+
17+
Layout/IndentationWidth:
18+
Width: 4
19+
20+
Layout/LineLength:
21+
Max: 200
22+
AllowedPatterns:
23+
- '^\s*#'
24+
25+
Layout/MultilineMethodCallIndentation:
26+
EnforcedStyle: indented

templates/ruby/Gemfile.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
source 'https://rubygems.org'
22

3-
gemspec
3+
gemspec

templates/ruby/base/params.twig

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,26 @@
99
{% for parameter in (method | parameters('all')) %}
1010
{% if parameter.required %}
1111
if {{ parameter.name | caseSnake | escapeKeyword }}.nil?
12-
raise {{spec.info.title | caseUcfirst}}::Exception.new('Missing required parameter: "{{ parameter.name | caseCamel | escapeKeyword }}"')
12+
raise {{spec.info.title | caseUcfirst}}::Exception.new('Missing required parameter: "{{ parameter.name | caseCamel | escapeKeyword }}"')
1313
end
1414

1515
{% endif %}
1616
{% endfor %}
17+
{% set requestParams = (method | parameters('query')) | merge((method | parameters('body'))) %}
18+
{% if requestParams | length == 0 %}
19+
api_params = {}
20+
{% else %}
1721
api_params = {
18-
{% for parameter in (method | parameters('query')) | merge((method | parameters('body'))) %}
22+
{% for parameter in requestParams %}
1923
{{ parameter.name }}: {{ parameter.name | caseSnake | escapeKeyword }},
2024
{% endfor %}
2125
}
22-
26+
{% endif %}
27+
28+
{% set headerEntries = (method | securityHeaders) | length + (method | parameters('header')) | length + (method | methodHeaders) | length %}
29+
{% if headerEntries == 0 %}
30+
api_headers = {}
31+
{% else %}
2332
api_headers = {
2433
{%~ for key, security in (method | securityHeaders) %}
2534
"{{ security.name }}": @client.get_config('{{ key | caseLower }}'),
@@ -31,3 +40,4 @@
3140
"{{ key }}": '{{ header }}',
3241
{% endfor %}
3342
}
43+
{% endif %}

templates/ruby/base/requests/api.twig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
{% endfor %}
2424

2525
raise Exception, "Unable to match response to any expected response model"
26-
{% else %}
26+
{%- else %}
2727
@client.call(
2828
method: '{{ method.method.value | caseUpper }}',
2929
path: api_path,
@@ -35,4 +35,4 @@
3535
response_type: Models::{{ (method | responseModel) | caseUcfirst }}
3636
{%~ endif %}
3737
)
38-
{% endif %}
38+
{%- endif %}

templates/ruby/base/requests/file.twig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
{% endfor %}
3232

3333
raise Exception, "Unable to match response to any expected response model"
34-
{% else %}
34+
{%- else %}
3535
@client.chunked_upload(
3636
path: api_path,
3737
headers: api_headers,
@@ -43,4 +43,4 @@
4343
response_type: Models::{{(method | responseModel) | caseUcfirst}}
4444
{% endif %}
4545
)
46-
{% endif %}
46+
{%- endif %}

templates/ruby/gemspec.twig

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
Gem::Specification.new do |spec|
2+
spec.name = '{{spec.info.title | caseLower | caseSnake}}'
3+
spec.version = '{{ sdk.version }}'
4+
spec.license = '{{ spec.info.license.name }}'
5+
spec.summary = '{{ sdk.shortDescription }}'
6+
spec.author = '{{ spec.info.contact.name }}'
7+
spec.homepage = '{{ spec.info.contact.url }}'
8+
spec.email = '{{ spec.info.contact.email }}'
9+
spec.files = Dir['lib/**/*.rb']
210

3-
spec.name = '{{spec.info.title | caseLower | caseSnake}}'
4-
spec.version = '{{ sdk.version }}'
5-
spec.license = '{{ spec.info.license.name }}'
6-
spec.summary = '{{ sdk.shortDescription }}'
7-
spec.author = '{{ spec.info.contact.name }}'
8-
spec.homepage = '{{ spec.info.contact.url }}'
9-
spec.email = '{{ spec.info.contact.email }}'
10-
spec.files = Dir['lib/**/*.rb']
11-
12-
spec.add_dependency 'mime-types', '~> 3.4.1'
13-
end
11+
spec.add_dependency 'mime-types', '~> 3.4.1'
12+
end

templates/ruby/lib/container/client.rb.twig

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#frozen_string_literal: true
1+
# frozen_string_literal: true
22

33
require 'net/http'
44
require 'uri'
@@ -7,15 +7,14 @@ require 'cgi'
77

88
module {{ spec.info.title | caseUcfirst }}
99
class Client
10-
1110
def initialize
12-
@chunk_size = 5*1024*1024
11+
@chunk_size = 5 * 1024 * 1024
1312
@headers = {
1413
'user-agent' => RUBY_PLATFORM + ':{{ language.name | caseLower }}-' + RUBY_VERSION,
15-
'x-sdk-name'=> '{{ sdk.name }}',
16-
'x-sdk-platform'=> '{{ sdk.platform }}',
17-
'x-sdk-language'=> '{{ language.name | caseLower }}',
18-
'x-sdk-version'=> '{{ sdk.version }}'{% if defaultHeaders | length > 0 %},{% endif %}
14+
'x-sdk-name' => '{{ sdk.name }}',
15+
'x-sdk-platform' => '{{ sdk.platform }}',
16+
'x-sdk-language' => '{{ language.name | caseLower }}',
17+
'x-sdk-version' => '{{ sdk.version }}'{% if defaultHeaders | length > 0 %},{% endif %}
1918

2019
{% for key,header in defaultHeaders %}
2120
'{{key}}' => '{{header}}'{% if not loop.last %},{% endif %}
@@ -225,13 +224,14 @@ module {{ spec.info.title | caseUcfirst }}
225224
chunks_uploaded.to_i >= chunks_total.to_i
226225
end
227226

228-
on_progress.call({
227+
progress = {
229228
id: result['$id'],
230-
progress: uploaded_size.to_f/size.to_f * 100.0,
229+
progress: uploaded_size.to_f / size.to_f * 100.0,
231230
size_uploaded: uploaded_size,
232231
chunks_total: result['chunksTotal'] || total_chunks,
233232
chunks_uploaded: result['chunksUploaded'] || completed_count
234-
}) unless on_progress.nil?
233+
}
234+
on_progress.call(progress) unless on_progress.nil?
235235

236236
mutex = Mutex.new
237237
queue = Queue.new
@@ -263,13 +263,14 @@ module {{ spec.info.title | caseUcfirst }}
263263
uploaded_size += chunk[:ending] - chunk[:start]
264264
last_result = chunk_result
265265
completed_result = chunk_result if upload_complete.call(chunk_result)
266-
on_progress.call({
266+
progress = {
267267
id: upload_id,
268-
progress: uploaded_size.to_f/size.to_f * 100.0,
268+
progress: uploaded_size.to_f / size.to_f * 100.0,
269269
size_uploaded: uploaded_size,
270270
chunks_total: chunk_result['chunksTotal'] || total_chunks,
271271
chunks_uploaded: chunk_result['chunksUploaded'] || completed_count
272-
}) unless on_progress.nil?
272+
}
273+
on_progress.call(progress) unless on_progress.nil?
273274
end
274275
end
275276
end
@@ -308,13 +309,13 @@ module {{ spec.info.title | caseUcfirst }}
308309
@boundary = "----A30#3ad1"
309310
if method != "GET"
310311
case headers[:'content-type']
311-
when 'application/json'
312-
payload = params.to_json
313-
when 'multipart/form-data'
314-
payload = encode_form_data(params) + "--#{@boundary}--\r\n"
315-
headers[:'content-type'] = "multipart/form-data; boundary=#{@boundary}"
316-
else
317-
payload = encode(params)
312+
when 'application/json'
313+
payload = params.to_json
314+
when 'multipart/form-data'
315+
payload = encode_form_data(params) + "--#{@boundary}--\r\n"
316+
headers[:'content-type'] = "multipart/form-data; boundary=#{@boundary}"
317+
else
318+
payload = encode(params)
318319
end
319320
end
320321

@@ -371,10 +372,10 @@ module {{ spec.info.title | caseUcfirst }}
371372
return response
372373
end
373374

374-
def encode_form_data(value, key=nil)
375+
def encode_form_data(value, key = nil)
375376
case value
376377
when Hash
377-
value.map { |k,v| encode_form_data(v,k) }.join
378+
value.map { |k, v| encode_form_data(v, k) }.join
378379
when Array
379380
value.map { |v| encode_form_data(v, "#{key}[]") }.join
380381
when nil
@@ -399,11 +400,11 @@ module {{ spec.info.title | caseUcfirst }}
399400

400401
def encode(value, key = nil)
401402
case value
402-
when Hash then value.map { |k,v| encode(v, append_key(key,k)) }.join('&')
403+
when Hash then value.map { |k, v| encode(v, append_key(key, k)) }.join('&')
403404
when Array then value.map { |v| encode(v, "#{key}[]") }.join('&')
404405
when nil then ''
405406
else
406-
"#{key}=#{CGI.escape(value.to_s)}"
407+
"#{key}=#{CGI.escape(value.to_s)}"
407408
end
408409
end
409410

0 commit comments

Comments
 (0)