thecompaniesapi 1.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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +4 -0
  3. data/.github/workflows/release.yml +106 -0
  4. data/.github/workflows/tests.yml +93 -0
  5. data/.gitignore +146 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +7 -0
  8. data/Gemfile.lock +36 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +551 -0
  11. data/Rakefile +25 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/thecompaniesapi/client.rb +643 -0
  15. data/lib/thecompaniesapi/error.rb +28 -0
  16. data/lib/thecompaniesapi/generated/models/action.rb +91 -0
  17. data/lib/thecompaniesapi/generated/models/company_v2.rb +136 -0
  18. data/lib/thecompaniesapi/generated/models/email_pattern.rb +51 -0
  19. data/lib/thecompaniesapi/generated/models/job_title.rb +81 -0
  20. data/lib/thecompaniesapi/generated/models/list.rb +141 -0
  21. data/lib/thecompaniesapi/generated/models/list_analytics.rb +46 -0
  22. data/lib/thecompaniesapi/generated/models/llmanswer.rb +66 -0
  23. data/lib/thecompaniesapi/generated/models/nominatim_city.rb +101 -0
  24. data/lib/thecompaniesapi/generated/models/nominatim_continent.rb +111 -0
  25. data/lib/thecompaniesapi/generated/models/nominatim_country.rb +121 -0
  26. data/lib/thecompaniesapi/generated/models/nominatim_county.rb +96 -0
  27. data/lib/thecompaniesapi/generated/models/nominatim_state.rb +96 -0
  28. data/lib/thecompaniesapi/generated/models/page_contents_ideated.rb +171 -0
  29. data/lib/thecompaniesapi/generated/models/page_contents_link.rb +41 -0
  30. data/lib/thecompaniesapi/generated/models/page_contents_page.rb +71 -0
  31. data/lib/thecompaniesapi/generated/models/pagination_meta.rb +76 -0
  32. data/lib/thecompaniesapi/generated/models/prompt.rb +91 -0
  33. data/lib/thecompaniesapi/generated/models/segmentation_condition.rb +56 -0
  34. data/lib/thecompaniesapi/generated/models/team.rb +101 -0
  35. data/lib/thecompaniesapi/generated/models/technology.rb +96 -0
  36. data/lib/thecompaniesapi/generated/models/user.rb +101 -0
  37. data/lib/thecompaniesapi/generated/operations_map.rb +11 -0
  38. data/lib/thecompaniesapi/generated/requests/ask_company_request.rb +60 -0
  39. data/lib/thecompaniesapi/generated/requests/count_companies_post_request.rb +50 -0
  40. data/lib/thecompaniesapi/generated/requests/create_list_request.rb +70 -0
  41. data/lib/thecompaniesapi/generated/requests/export_companies_analytics_request.rb +70 -0
  42. data/lib/thecompaniesapi/generated/requests/fetch_companies_in_list_post_request.rb +70 -0
  43. data/lib/thecompaniesapi/generated/requests/product_prompt_request.rb +65 -0
  44. data/lib/thecompaniesapi/generated/requests/prompt_to_segmentation_request.rb +55 -0
  45. data/lib/thecompaniesapi/generated/requests/request_action_request.rb +80 -0
  46. data/lib/thecompaniesapi/generated/requests/retry_action_request.rb +30 -0
  47. data/lib/thecompaniesapi/generated/requests/search_companies_post_request.rb +90 -0
  48. data/lib/thecompaniesapi/generated/requests/toggle_companies_in_list_request.rb +50 -0
  49. data/lib/thecompaniesapi/generated/requests/update_list_request.rb +65 -0
  50. data/lib/thecompaniesapi/generated/requests/update_team_request.rb +45 -0
  51. data/lib/thecompaniesapi/generated/responses/ask_company_response.rb +38 -0
  52. data/lib/thecompaniesapi/generated/responses/count_companies_post_response.rb +34 -0
  53. data/lib/thecompaniesapi/generated/responses/count_companies_response.rb +34 -0
  54. data/lib/thecompaniesapi/generated/responses/create_list_response.rb +25 -0
  55. data/lib/thecompaniesapi/generated/responses/delete_list_response.rb +25 -0
  56. data/lib/thecompaniesapi/generated/responses/delete_prompt_response.rb +25 -0
  57. data/lib/thecompaniesapi/generated/responses/enrich_job_titles_response.rb +25 -0
  58. data/lib/thecompaniesapi/generated/responses/export_companies_analytics_response.rb +38 -0
  59. data/lib/thecompaniesapi/generated/responses/fetch_actions_response.rb +38 -0
  60. data/lib/thecompaniesapi/generated/responses/fetch_api_health_response.rb +42 -0
  61. data/lib/thecompaniesapi/generated/responses/fetch_companies_analytics_response.rb +38 -0
  62. data/lib/thecompaniesapi/generated/responses/fetch_companies_in_list_post_response.rb +42 -0
  63. data/lib/thecompaniesapi/generated/responses/fetch_companies_in_list_response.rb +42 -0
  64. data/lib/thecompaniesapi/generated/responses/fetch_company_by_email_response.rb +42 -0
  65. data/lib/thecompaniesapi/generated/responses/fetch_company_by_social_response.rb +25 -0
  66. data/lib/thecompaniesapi/generated/responses/fetch_company_context_response.rb +38 -0
  67. data/lib/thecompaniesapi/generated/responses/fetch_company_email_patterns_response.rb +25 -0
  68. data/lib/thecompaniesapi/generated/responses/fetch_company_in_list_response.rb +25 -0
  69. data/lib/thecompaniesapi/generated/responses/fetch_company_response.rb +25 -0
  70. data/lib/thecompaniesapi/generated/responses/fetch_lists_response.rb +38 -0
  71. data/lib/thecompaniesapi/generated/responses/fetch_open_api_response.rb +30 -0
  72. data/lib/thecompaniesapi/generated/responses/fetch_prompts_response.rb +38 -0
  73. data/lib/thecompaniesapi/generated/responses/fetch_team_response.rb +25 -0
  74. data/lib/thecompaniesapi/generated/responses/fetch_user_response.rb +25 -0
  75. data/lib/thecompaniesapi/generated/responses/product_prompt_response.rb +42 -0
  76. data/lib/thecompaniesapi/generated/responses/prompt_to_segmentation_response.rb +42 -0
  77. data/lib/thecompaniesapi/generated/responses/request_action_response.rb +34 -0
  78. data/lib/thecompaniesapi/generated/responses/retry_action_response.rb +34 -0
  79. data/lib/thecompaniesapi/generated/responses/search_cities_response.rb +38 -0
  80. data/lib/thecompaniesapi/generated/responses/search_companies_by_name_response.rb +38 -0
  81. data/lib/thecompaniesapi/generated/responses/search_companies_by_prompt_response.rb +38 -0
  82. data/lib/thecompaniesapi/generated/responses/search_companies_post_response.rb +42 -0
  83. data/lib/thecompaniesapi/generated/responses/search_companies_response.rb +42 -0
  84. data/lib/thecompaniesapi/generated/responses/search_continents_response.rb +38 -0
  85. data/lib/thecompaniesapi/generated/responses/search_counties_response.rb +38 -0
  86. data/lib/thecompaniesapi/generated/responses/search_countries_response.rb +38 -0
  87. data/lib/thecompaniesapi/generated/responses/search_industries_response.rb +38 -0
  88. data/lib/thecompaniesapi/generated/responses/search_industries_similar_response.rb +38 -0
  89. data/lib/thecompaniesapi/generated/responses/search_similar_companies_response.rb +38 -0
  90. data/lib/thecompaniesapi/generated/responses/search_states_response.rb +38 -0
  91. data/lib/thecompaniesapi/generated/responses/search_technologies_response.rb +38 -0
  92. data/lib/thecompaniesapi/generated/responses/toggle_companies_in_list_response.rb +25 -0
  93. data/lib/thecompaniesapi/generated/responses/update_list_response.rb +25 -0
  94. data/lib/thecompaniesapi/generated/responses/update_team_response.rb +25 -0
  95. data/lib/thecompaniesapi/http_client.rb +146 -0
  96. data/lib/thecompaniesapi/version.rb +3 -0
  97. data/lib/thecompaniesapi.rb +7 -0
  98. data/script/generate_client.rb +653 -0
  99. data/thecompaniesapi.gemspec +34 -0
  100. metadata +188 -0
@@ -0,0 +1,653 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'uri'
7
+ require 'fileutils'
8
+
9
+ ##
10
+ # OpenAPI Client Generator for The Companies API Ruby SDK
11
+ #
12
+ # This script fetches the OpenAPI schema and generates operation methods
13
+ # that integrate with the existing Client class architecture.
14
+ class OpenAPIClientGenerator
15
+ DEFAULT_OPENAPI_URL = 'https://api.thecompaniesapi.com/v2/openapi'
16
+
17
+ attr_reader :openapi_url, :output_path, :schema
18
+
19
+ def initialize(openapi_url = DEFAULT_OPENAPI_URL, output_path = 'lib/thecompaniesapi/generated')
20
+ @openapi_url = openapi_url
21
+ @output_path = output_path
22
+ @schema = nil
23
+ end
24
+
25
+ def generate
26
+ puts "🚀 Generating Ruby SDK from OpenAPI schema...\n\n"
27
+
28
+ # Step 1: Fetch OpenAPI schema
29
+ fetch_schema
30
+
31
+ # Step 2: Generate operations map
32
+ operations = generate_operations_map
33
+
34
+ # Step 3: Generate model classes
35
+ generate_models
36
+
37
+ # Step 4: Generate API request classes
38
+ generate_api_requests(operations)
39
+
40
+ # Step 5: Generate API response classes
41
+ generate_api_responses(operations)
42
+
43
+ # Step 6: Generate client methods
44
+ client_methods = generate_client_methods(operations)
45
+
46
+ # Step 7: Create the generated client class
47
+ create_generated_client(client_methods, operations)
48
+
49
+ puts "✅ Generation complete!"
50
+ puts "📁 Generated files in: #{@output_path}"
51
+ end
52
+
53
+ private
54
+
55
+ def fetch_schema
56
+ puts "📥 Fetching OpenAPI schema from #{@openapi_url}..."
57
+
58
+ uri = URI(@openapi_url)
59
+
60
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
61
+ request = Net::HTTP::Get.new(uri)
62
+ request['User-Agent'] = 'TheCompaniesApi-Ruby-SDK-Generator/1.0'
63
+
64
+ response = http.request(request)
65
+
66
+ unless response.code == '200'
67
+ raise "Failed to fetch OpenAPI schema: HTTP #{response.code}"
68
+ end
69
+
70
+ @schema = JSON.parse(response.body)
71
+ end
72
+
73
+ puts "✅ Schema fetched successfully"
74
+ rescue => e
75
+ raise "Error fetching schema: #{e.message}"
76
+ end
77
+
78
+ def generate_operations_map
79
+ puts "🔧 Generating operations map..."
80
+
81
+ operations = {}
82
+ paths = @schema['paths'] || {}
83
+
84
+ paths.each do |path, path_data|
85
+ %w[get post put patch delete].each do |method|
86
+ next unless path_data[method]
87
+
88
+ operation = path_data[method]
89
+ operation_id = operation['operationId']
90
+
91
+ next unless operation_id
92
+
93
+ # Extract path parameters
94
+ path_params = path.scan(/\{([^}]+)\}/).flatten
95
+
96
+ operations[operation_id] = {
97
+ 'path' => path,
98
+ 'method' => method.upcase,
99
+ 'path_params' => path_params,
100
+ 'summary' => operation['summary'] || '',
101
+ 'description' => operation['description'] || '',
102
+ 'parameters' => operation['parameters'] || [],
103
+ 'request_body' => operation['requestBody'],
104
+ 'responses' => operation['responses'] || {}
105
+ }
106
+ end
107
+ end
108
+
109
+ puts "✅ Generated #{operations.size} operations"
110
+ operations
111
+ end
112
+
113
+ def generate_models
114
+ puts "📋 Generating model classes..."
115
+
116
+ schemas = @schema.dig('components', 'schemas') || {}
117
+ model_count = 0
118
+
119
+ ensure_directory("#{@output_path}/models")
120
+
121
+ schemas.each do |schema_name, schema_data|
122
+ generate_model_class(schema_name, schema_data)
123
+ model_count += 1
124
+ end
125
+
126
+ puts "✅ Generated #{model_count} model classes"
127
+ end
128
+
129
+ def generate_model_class(name, schema)
130
+ class_name = to_pascal_case(name)
131
+ properties = schema['properties'] || {}
132
+ required = schema['required'] || []
133
+
134
+ class_content = <<~RUBY
135
+ # frozen_string_literal: true
136
+
137
+ module TheCompaniesAPI
138
+ module Generated
139
+ module Models
140
+ ##
141
+ # #{class_name} model
142
+ #
143
+ RUBY
144
+
145
+ if schema['description']
146
+ class_content += " # #{schema['description']}\n"
147
+ end
148
+
149
+ class_content += <<~RUBY
150
+ class #{class_name}
151
+ RUBY
152
+
153
+ # Generate attr_accessors
154
+ properties.each do |prop_name, prop_data|
155
+ class_content += " # @return [#{get_ruby_type(prop_data)}] #{prop_data['description'] || prop_name}\n"
156
+ class_content += " attr_accessor :#{to_snake_case(prop_name)}\n\n"
157
+ end
158
+
159
+ # Generate constructor
160
+ class_content += <<~RUBY
161
+ ##
162
+ # Initialize a new #{class_name}
163
+ #
164
+ # @param data [Hash] Initial data
165
+ def initialize(data = {})
166
+ RUBY
167
+
168
+ properties.each do |prop_name, prop_data|
169
+ snake_prop = to_snake_case(prop_name)
170
+ ruby_type = get_ruby_type(prop_data)
171
+
172
+ if ruby_type == 'Time'
173
+ class_content += " @#{snake_prop} = data['#{prop_name}'] ? Time.parse(data['#{prop_name}'].to_s) : nil\n"
174
+ else
175
+ class_content += " @#{snake_prop} = data['#{prop_name}']\n"
176
+ end
177
+ end
178
+
179
+ class_content += <<~RUBY
180
+ end
181
+
182
+ ##
183
+ # Convert to hash
184
+ #
185
+ # @return [Hash] Hash representation
186
+ def to_hash
187
+ {
188
+ RUBY
189
+
190
+ properties.each do |prop_name, prop_data|
191
+ snake_prop = to_snake_case(prop_name)
192
+ ruby_type = get_ruby_type(prop_data)
193
+
194
+ if ruby_type == 'Time'
195
+ class_content += " '#{prop_name}' => @#{snake_prop}&.iso8601,\n"
196
+ else
197
+ class_content += " '#{prop_name}' => @#{snake_prop},\n"
198
+ end
199
+ end
200
+
201
+ class_content += <<~RUBY
202
+ }
203
+ end
204
+
205
+ alias_method :to_h, :to_hash
206
+ end
207
+ end
208
+ end
209
+ end
210
+ RUBY
211
+
212
+ File.write("#{@output_path}/models/#{to_snake_case(class_name)}.rb", class_content)
213
+ end
214
+
215
+ def generate_api_requests(operations)
216
+ puts "📤 Generating API request classes..."
217
+
218
+ request_count = 0
219
+ ensure_directory("#{@output_path}/requests")
220
+
221
+ operations.each do |operation_id, operation|
222
+ request_body = operation['request_body']
223
+
224
+ next unless request_body&.dig('content', 'application/json', 'schema')
225
+
226
+ schema = request_body['content']['application/json']['schema']
227
+ class_name = "#{to_pascal_case(operation_id)}Request"
228
+
229
+ generate_request_class(class_name, schema, operation_id)
230
+ request_count += 1
231
+ end
232
+
233
+ puts "✅ Generated #{request_count} API request classes"
234
+ end
235
+
236
+ def generate_request_class(class_name, schema, operation_id)
237
+ properties = schema['properties'] || {}
238
+ required = schema['required'] || []
239
+
240
+ class_content = <<~RUBY
241
+ # frozen_string_literal: true
242
+
243
+ module TheCompaniesAPI
244
+ module Generated
245
+ module Requests
246
+ ##
247
+ # #{class_name} - API request class
248
+ #
249
+ RUBY
250
+
251
+ if schema['description']
252
+ class_content += " # #{schema['description']}\n #\n"
253
+ end
254
+
255
+ class_content += <<~RUBY
256
+ class #{class_name}
257
+ RUBY
258
+
259
+ # Generate attr_accessors
260
+ properties.each do |prop_name, prop_data|
261
+ snake_prop = to_snake_case(prop_name)
262
+ class_content += " # @return [#{get_ruby_type(prop_data)}] #{prop_data['description'] || prop_name}\n"
263
+ class_content += " attr_accessor :#{snake_prop}\n\n"
264
+ end
265
+
266
+ # Generate constructor
267
+ class_content += <<~RUBY
268
+ ##
269
+ # Initialize a new #{class_name}
270
+ #
271
+ # @param data [Hash] Request data
272
+ def initialize(data = {})
273
+ RUBY
274
+
275
+ properties.each do |prop_name, prop_data|
276
+ snake_prop = to_snake_case(prop_name)
277
+ ruby_type = get_ruby_type(prop_data)
278
+
279
+ if ruby_type == 'Time'
280
+ class_content += " @#{snake_prop} = data['#{prop_name}'] || data[:#{snake_prop}]\n"
281
+ class_content += " @#{snake_prop} = Time.parse(@#{snake_prop}.to_s) if @#{snake_prop}.is_a?(String)\n"
282
+ else
283
+ class_content += " @#{snake_prop} = data['#{prop_name}'] || data[:#{snake_prop}]\n"
284
+ end
285
+ end
286
+
287
+ class_content += <<~RUBY
288
+ end
289
+
290
+ ##
291
+ # Convert to hash for API request
292
+ #
293
+ # @return [Hash] Hash representation
294
+ def to_hash
295
+ {
296
+ RUBY
297
+
298
+ properties.each do |prop_name, prop_data|
299
+ snake_prop = to_snake_case(prop_name)
300
+ ruby_type = get_ruby_type(prop_data)
301
+
302
+ if ruby_type == 'Time'
303
+ class_content += " '#{prop_name}' => @#{snake_prop}&.iso8601,\n"
304
+ else
305
+ class_content += " '#{prop_name}' => @#{snake_prop},\n"
306
+ end
307
+ end
308
+
309
+ class_content += <<~RUBY
310
+ }.compact
311
+ end
312
+
313
+ alias_method :to_h, :to_hash
314
+ end
315
+ end
316
+ end
317
+ end
318
+ RUBY
319
+
320
+ File.write("#{@output_path}/requests/#{to_snake_case(class_name)}.rb", class_content)
321
+ end
322
+
323
+ def generate_api_responses(operations)
324
+ puts "📥 Generating API response classes..."
325
+
326
+ response_count = 0
327
+ ensure_directory("#{@output_path}/responses")
328
+
329
+ operations.each do |operation_id, operation|
330
+ responses = operation['responses']
331
+
332
+ # Look for successful responses (200, 201, etc.)
333
+ [200, 201, 202].each do |status_code|
334
+ response = responses[status_code.to_s]
335
+ next unless response&.dig('content', 'application/json', 'schema')
336
+
337
+ schema = response['content']['application/json']['schema']
338
+ class_name = "#{to_pascal_case(operation_id)}Response"
339
+
340
+ generate_response_class(class_name, schema, operation)
341
+ response_count += 1
342
+ break # Only generate for the first successful response
343
+ end
344
+ end
345
+
346
+ puts "✅ Generated #{response_count} API response classes"
347
+ end
348
+
349
+ def generate_response_class(class_name, schema, operation)
350
+ class_content = <<~RUBY
351
+ # frozen_string_literal: true
352
+
353
+ module TheCompaniesAPI
354
+ module Generated
355
+ module Responses
356
+ ##
357
+ # #{class_name} - API response class
358
+ #
359
+ RUBY
360
+
361
+ if operation['summary']
362
+ class_content += " # #{operation['summary']}\n #\n"
363
+ end
364
+
365
+ class_content += " class #{class_name}\n"
366
+
367
+ if schema['type'] == 'object' && schema['properties']
368
+ # Object response with properties
369
+ properties = schema['properties']
370
+
371
+ properties.each do |prop_name, prop_data|
372
+ snake_prop = to_snake_case(prop_name)
373
+ class_content += " # @return [#{get_ruby_type(prop_data)}] #{prop_data['description'] || prop_name}\n"
374
+ class_content += " attr_reader :#{snake_prop}\n\n"
375
+ end
376
+
377
+ class_content += <<~RUBY
378
+ ##
379
+ # Initialize a new #{class_name}
380
+ #
381
+ # @param data [Hash] Response data
382
+ def initialize(data = {})
383
+ RUBY
384
+
385
+ properties.each do |prop_name, prop_data|
386
+ snake_prop = to_snake_case(prop_name)
387
+ class_content += " @#{snake_prop} = convert_property(data['#{prop_name}'], '#{prop_name}')\n"
388
+ end
389
+
390
+ class_content += <<~RUBY
391
+ end
392
+
393
+ private
394
+
395
+ def convert_property(value, property)
396
+ case property
397
+ RUBY
398
+
399
+ # Add specific conversions for typed properties
400
+ properties.each do |prop_name, prop_data|
401
+ ruby_type = get_ruby_type(prop_data)
402
+ if ruby_type.start_with?('TheCompaniesAPI::Generated::Models::')
403
+ class_content += " when '#{prop_name}'\n"
404
+ class_content += " value.is_a?(Hash) ? #{ruby_type}.new(value) : value\n"
405
+ end
406
+ end
407
+
408
+ class_content += <<~RUBY
409
+ else
410
+ value
411
+ end
412
+ end
413
+ RUBY
414
+
415
+ elsif schema['type'] == 'array'
416
+ class_content += <<~RUBY
417
+ # @return [Array] Response data
418
+ attr_reader :data
419
+
420
+ ##
421
+ # Initialize a new #{class_name}
422
+ #
423
+ # @param data [Array] Response data
424
+ def initialize(data = [])
425
+ @data = data
426
+ end
427
+ RUBY
428
+ else
429
+ class_content += <<~RUBY
430
+ # @return [Object] Response data
431
+ attr_reader :data
432
+
433
+ ##
434
+ # Initialize a new #{class_name}
435
+ #
436
+ # @param data [Object] Response data
437
+ def initialize(data = nil)
438
+ @data = data
439
+ end
440
+ RUBY
441
+ end
442
+
443
+ class_content += " end\n end\n end\n end\n"
444
+
445
+ File.write("#{@output_path}/responses/#{to_snake_case(class_name)}.rb", class_content)
446
+ end
447
+
448
+ def generate_client_methods(operations)
449
+ puts "🏗️ Generating client methods..."
450
+
451
+ methods = []
452
+
453
+ operations.each do |operation_id, operation|
454
+ method_name = to_snake_case(operation_id)
455
+ path = operation['path']
456
+ http_method = operation['method']
457
+ path_params = operation['path_params']
458
+ summary = operation['summary']
459
+ description = operation['description']
460
+ request_body = operation['request_body']
461
+ responses = operation['responses']
462
+
463
+ # Generate method
464
+ method_content = generate_method(
465
+ method_name, path, http_method, path_params,
466
+ summary, description, request_body, responses, operation_id
467
+ )
468
+
469
+ methods << method_content
470
+ end
471
+
472
+ puts "✅ Generated #{methods.size} client methods"
473
+ methods.join("\n\n")
474
+ end
475
+
476
+ def generate_method(method_name, path, http_method, path_params, summary, description, request_body, responses, operation_id)
477
+ # Generate YARD documentation
478
+ doc_lines = [" ##", " # #{summary}"]
479
+ doc_lines << " #" if summary != description && !description.empty?
480
+ doc_lines << " # #{description}" if !description.empty? && description != summary
481
+ doc_lines << " #"
482
+
483
+ # Add path parameters to documentation
484
+ path_params.each do |param|
485
+ doc_lines << " # @param #{to_snake_case(param)} [String] Path parameter"
486
+ end
487
+
488
+ if %w[GET DELETE].include?(http_method)
489
+ doc_lines << " # @param params [Hash] Query parameters"
490
+ else
491
+ doc_lines << " # @param data [Hash] Request body data"
492
+ end
493
+
494
+ doc_lines << " # @param headers [Hash] Additional headers"
495
+ doc_lines << " # @return [Hash] API response"
496
+
497
+ # Generate method signature
498
+ signature_params = []
499
+ path_params.each { |param| signature_params << "#{to_snake_case(param)}" }
500
+
501
+ if %w[GET DELETE].include?(http_method)
502
+ signature_params << "params: {}"
503
+ else
504
+ signature_params << "data: {}"
505
+ end
506
+
507
+ signature_params << "headers: {}"
508
+ signature = "def #{method_name}(#{signature_params.join(', ')})"
509
+
510
+ # Generate method body
511
+ body_lines = []
512
+
513
+ # Build endpoint path with parameter substitution
514
+ if path_params.any?
515
+ path_expression = path.dup
516
+ path_params.each do |param|
517
+ snake_param = to_snake_case(param)
518
+ path_expression = path_expression.gsub("{#{param}}", "\#{#{snake_param}}")
519
+ end
520
+ body_lines << " endpoint = \"#{path_expression}\""
521
+ else
522
+ body_lines << " endpoint = '#{path}'"
523
+ end
524
+
525
+ body_lines << ""
526
+
527
+ # Make the HTTP request
528
+ if %w[GET DELETE].include?(http_method)
529
+ body_lines << " #{http_method.downcase}(endpoint, params: params)"
530
+ else
531
+ body_lines << " #{http_method.downcase}(endpoint, body: data)"
532
+ end
533
+
534
+ # Combine everything
535
+ [
536
+ doc_lines.join("\n"),
537
+ " #{signature}",
538
+ body_lines.map { |line| " #{line}" }.join("\n"),
539
+ " end"
540
+ ].join("\n")
541
+ end
542
+
543
+ def create_generated_client(methods, operations)
544
+ puts "🏗️ Creating generated client class..."
545
+
546
+ # Generate the main Client class with all API methods
547
+ class_content = <<~RUBY
548
+ # frozen_string_literal: true
549
+
550
+ require_relative 'http_client'
551
+
552
+ module TheCompaniesAPI
553
+ ##
554
+ # The Companies API client with auto-generated methods
555
+ #
556
+ # This class extends HttpClient with auto-generated methods
557
+ # from the OpenAPI specification.
558
+ class Client < HttpClient
559
+ #{methods}
560
+ end
561
+ end
562
+ RUBY
563
+
564
+ # Write to the main client file, not the generated subdirectory
565
+ File.write("#{File.dirname(@output_path)}/client.rb", class_content)
566
+
567
+ # Create operations map in the generated directory for reference
568
+ operations_content = <<~RUBY
569
+ # frozen_string_literal: true
570
+
571
+ module TheCompaniesAPI
572
+ module Generated
573
+ ##
574
+ # Operations map auto-generated from OpenAPI schema
575
+ module OperationsMap
576
+ OPERATIONS = #{operations.inspect}
577
+ end
578
+ end
579
+ end
580
+ RUBY
581
+
582
+ File.write("#{@output_path}/operations_map.rb", operations_content)
583
+ end
584
+
585
+ # Helper methods
586
+ def to_snake_case(string)
587
+ string
588
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
589
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
590
+ .downcase
591
+ end
592
+
593
+ def to_pascal_case(string)
594
+ # Handle camelCase, snake_case, and kebab-case
595
+ string
596
+ .gsub(/([a-z])([A-Z])/, '\1_\2') # Insert underscore between camelCase
597
+ .split(/[-_]/) # Split on hyphens and underscores
598
+ .map(&:capitalize) # Capitalize each word
599
+ .join # Join without separators
600
+ end
601
+
602
+ def get_ruby_type(property)
603
+ type = property['type']
604
+ format = property['format']
605
+
606
+ return 'Object' unless type
607
+
608
+ case type
609
+ when 'string'
610
+ case format
611
+ when 'date', 'date-time'
612
+ 'Time'
613
+ else
614
+ 'String'
615
+ end
616
+ when 'integer'
617
+ 'Integer'
618
+ when 'number'
619
+ 'Float'
620
+ when 'boolean'
621
+ 'Boolean'
622
+ when 'array'
623
+ 'Array'
624
+ when 'object'
625
+ 'Hash'
626
+ else
627
+ 'Object'
628
+ end
629
+ end
630
+
631
+ def ensure_directory(path)
632
+ FileUtils.mkdir_p(path) unless Dir.exist?(path)
633
+ end
634
+ end
635
+
636
+ # CLI Usage
637
+ if ARGV.empty?
638
+ puts "Usage: ruby generate_client.rb [openapi-url] [output-dir]"
639
+ puts "Example: ruby generate_client.rb https://api.thecompaniesapi.com/v2/openapi lib/thecompaniesapi/generated"
640
+ exit 1
641
+ end
642
+
643
+ openapi_url = ARGV[0] || OpenAPIClientGenerator::DEFAULT_OPENAPI_URL
644
+ output_dir = ARGV[1] || 'lib/thecompaniesapi/generated'
645
+
646
+ begin
647
+ generator = OpenAPIClientGenerator.new(openapi_url, output_dir)
648
+ generator.generate
649
+ rescue => e
650
+ puts "❌ Error: #{e.message}"
651
+ puts e.backtrace.join("\n") if ENV['DEBUG']
652
+ exit 1
653
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'lib/thecompaniesapi/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "thecompaniesapi"
5
+ spec.version = TheCompaniesAPI::VERSION
6
+ spec.authors = ["The Companies API SARL"]
7
+ spec.email = ["yael@thecompaniesapi.com"]
8
+
9
+ spec.summary = 'Ruby SDK for The Companies API'
10
+ spec.description = 'A fully-featured Ruby SDK for The Companies API, providing type-safe access to company data, locations, industries, technologies, job titles, lists, and more.'
11
+ spec.homepage = "https://github.com/thecompaniesapi/sdk-ruby"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["homepage_uri"] = "https://www.thecompaniesapi.com"
16
+ spec.metadata["source_code_uri"] = "https://github.com/thecompaniesapi/sdk-ruby"
17
+ spec.metadata["changelog_uri"] = "https://github.com/thecompaniesapi/sdk-ruby/releases"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` command works on MacOS to list files tracked by git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ # Runtime dependencies
29
+ spec.add_runtime_dependency "faraday", "~> 2.0"
30
+ spec.add_runtime_dependency "faraday-net_http", "~> 3.0"
31
+
32
+ # Development dependencies
33
+ spec.add_development_dependency "dotenv", "~> 2.8"
34
+ end