docit 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19ca1ea84419aa3ec1c1605fec67c6355027d866de1b9e6b0d8fb27472b91602
4
- data.tar.gz: a8fc557d32361a3729a9133791100bea3c7bf94e4c2f0ca55351eff4760efafe
3
+ metadata.gz: 39589c6800b65d7a0ad55701378b345f18277735df3511cce965bc889035e74f
4
+ data.tar.gz: 91c392bded5bbd0897222859feab4d68e8c0d3064d4054e6b75509a325ced2d0
5
5
  SHA512:
6
- metadata.gz: 4614b2cc382920b578400ddbccb8afb0eb8a32830307e0fd75fc3a175678f218d7f658e82b61678422d520c6f6c841fa5643d36d563b6cacf0bd4233bbed655f
7
- data.tar.gz: fe46a50539ba08a2783b8e1f4d95a2e313fc1a043f78dee017ad4dd47cba8166f5cb8341bf5cbf732b4e7f7a776e9966e12efcce4c1a1b3a3b2c08cf2887acf1
6
+ metadata.gz: 1ea253c903e6a90eb3754ed9f4a586adcc7339c831d9623401cc4bb53256359c81e069bba4067a12379da350fbf354b50bfd14420ae0aa75c3841c74942ae5f2
7
+ data.tar.gz: 8441b41ae22a25f1bd29514fbdcb425b4bbd56b591da6904df0c9ad070a7ac830b1eb9338783a8c751d8f648db4826d69b7755a0f42cd320baaf8e3ebeb7c628
@@ -11,7 +11,7 @@ A clear and concise description of the bug.
11
11
  ## Steps to Reproduce
12
12
 
13
13
  1. Configure Docit with `...`
14
- 2. Add `swagger_doc` to `...`
14
+ 2. Add `doc_for` to `...`
15
15
  3. Visit `/api-docs`
16
16
  4. See error
17
17
 
@@ -17,7 +17,7 @@ Explain the problem this feature would solve or the workflow it would improve.
17
17
  Describe how you'd like it to work, including example DSL usage if applicable:
18
18
 
19
19
  ```ruby
20
- swagger_doc :index do
20
+ doc_for :index do
21
21
  # your proposed syntax here
22
22
  end
23
23
  ```
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2026-04-18
4
+
5
+ ### Added
6
+ - `operation_id` DSL method: set a custom `operationId` per endpoint for cleaner SDK codegen
7
+ - Auto-generated `operationId` for every endpoint (e.g., `index_users`, `login_auth`) when not explicitly set
8
+ - `config.license` option: add license info (`name`, `url`) to the OpenAPI spec
9
+ - `config.contact` option: add contact info (`name`, `email`, `url`) to the OpenAPI spec
10
+ - `config.terms_of_service` option: add terms of service URL to the OpenAPI spec
11
+
12
+ ### Changed
13
+ - Renamed `swagger_doc` DSL to `doc_for` (`swagger_doc` still works as a backward-compatible alias)
14
+
15
+ ### Fixed
16
+ - `TagInjector` no longer crashes when `initializer_path` is nil
17
+ - `docit:autodoc` rake task: removed broken `--dry-run` CLI flag (use `DRY_RUN=1` env var instead)
18
+ - Fixed dummy app `auth_docs.rb` summary and request body to match integration test expectations
19
+
20
+ ## [0.3.1] - 2026-04-17
21
+
22
+ ### Fixed
23
+ - Fixed gem packaging: `doc_block_validator.rb` was missing from the published 0.3.0 gem, causing `LoadError` on require
24
+
3
25
  ## [0.3.0] - 2026-04-16
4
26
 
5
27
  ### Added
@@ -51,7 +73,7 @@
51
73
  ## [0.1.0] - 2026-04-08
52
74
 
53
75
  - Initial release
54
- - DSL: `swagger_doc` macro for inline controller documentation
76
+ - DSL: `doc_for` macro for inline controller documentation
55
77
  - DSL: `use_docs` + `Docit::DocFile` for separate doc files
56
78
  - Builders: request body, response, and parameter builders with nested object/array support
57
79
  - Schema `$ref` components via `Docit.define_schema`
data/CONTRIBUTING.md CHANGED
@@ -55,7 +55,7 @@ bundle exec rspec spec/docit/v2_features_spec.rb:30
55
55
  lib/docit.rb # Entry point, configuration, schema registry
56
56
  lib/docit/configuration.rb # Config class (title, auth, tags)
57
57
  lib/docit/registry.rb # Global operation store
58
- lib/docit/dsl.rb # swagger_doc macro
58
+ lib/docit/dsl.rb # doc_for macro
59
59
  lib/docit/operation.rb # Single endpoint documentation
60
60
  lib/docit/builders/ # DSL builders (response, request_body, parameter)
61
61
  lib/docit/schema_definition.rb # Reusable $ref schema definitions
data/README.md CHANGED
@@ -5,44 +5,13 @@
5
5
 
6
6
  Decorator-style API documentation for Ruby on Rails. Write OpenAPI 3.0.3 docs with clean controller DSL macros, separate doc modules, or AI-assisted scaffolding for undocumented endpoints.
7
7
 
8
- ## Table Of Contents
9
-
10
- - Getting started
11
- - [Installation](#installation)
12
- - [Configuration](#configuration)
13
- - [Usage](#usage)
14
- - Documentation styles
15
- - [Style 1: Inline (simple APIs)](#style-1-inline-simple-apis)
16
- - [Style 2: Separate doc files (recommended for larger APIs)](#style-2-separate-doc-files-recommended-for-larger-apis)
17
- - Endpoint DSL reference
18
- - [Endpoint documentation DSL](#endpoint-documentation-dsl)
19
- - [Request bodies](#request-bodies)
20
- - [Path parameters](#path-parameters)
21
- - [Enums](#enums)
22
- - [Security](#security)
23
- - [Deprecated endpoints](#deprecated-endpoints)
24
- - [Nested objects and arrays](#nested-objects-and-arrays)
25
- - [Response examples](#response-examples)
26
- - [Shared schemas (`$ref`)](#shared-schemas-ref)
27
- - [File uploads](#file-uploads)
28
- - AI documentation
29
- - [AI Automatic Documentation](#ai-automatic-documentation)
30
- - [Quick start (included in install)](#quick-start-included-in-install)
31
- - [Standalone commands](#standalone-commands)
32
- - [Supported providers](#supported-providers)
33
- - [What the AI generates](#what-the-ai-generates)
34
- - Runtime and development
35
- - [Documentation UIs](#documentation-uis)
36
- - [How it works](#how-it-works)
37
- - [Mounting at a different path](#mounting-at-a-different-path)
38
- - [JSON spec only](#json-spec-only)
39
- - [Development](#development)
40
- - [Contributing](#contributing)
41
- - [License](#license)
42
- - Project docs
43
- - [CHANGELOG](CHANGELOG.md)
44
- - [CONTRIBUTING](CONTRIBUTING.md)
45
- - [CODE OF CONDUCT](CODE_OF_CONDUCT.md)
8
+ ### Scalar (default)
9
+ ![Scalar API Reference](docs/images/scalar_image.png)
10
+
11
+ ### Swagger
12
+ ![Swagger UI](docs/images/swagger_image.png)
13
+
14
+ > **Full documentation:** [docitruby.dev/docs](https://docitruby.dev/docs)
46
15
 
47
16
  ## Installation
48
17
 
@@ -70,8 +39,6 @@ The install generator does everything in one step:
70
39
 
71
40
  Visit `/api-docs` to see your interactive API documentation (Scalar by default, Swagger UI also available at `/api-docs/swagger`).
72
41
 
73
- If you choose AI setup, Docit stores your provider config in `.docit_ai.yml` with restricted file permissions and adds that file to `.gitignore` when possible.
74
-
75
42
  ## Configuration
76
43
 
77
44
  Edit `config/initializers/docit.rb`:
@@ -81,48 +48,40 @@ Docit.configure do |config|
81
48
  config.title = "My API"
82
49
  config.version = "1.0.0"
83
50
  config.description = "Backend API documentation"
51
+ config.default_ui = :scalar # :scalar (default) or :swagger
84
52
 
85
- # Documentation UI: :scalar (default) or :swagger
86
- config.default_ui = :scalar
87
-
88
- # Authentication: pick one (or multiple):
89
- config.auth :bearer # Bearer token (JWT by default)
90
- config.auth :basic # HTTP Basic
91
- config.auth :api_key, name: "X-API-Key", # API key in header
92
- location: "header"
93
-
94
- # Tag descriptions (shown in the documentation sidebar):
53
+ config.auth :bearer # Bearer token (JWT)
95
54
  config.tag "Users", description: "User account management"
96
- config.tag "Auth", description: "Authentication endpoints"
97
-
98
- # Server URLs (shown in the server dropdown):
99
55
  config.server "https://api.example.com", description: "Production"
100
- config.server "https://staging.example.com", description: "Staging"
101
- config.server "http://localhost:3000", description: "Development"
102
56
  end
103
57
  ```
104
58
 
105
- ## Usage
59
+ See the [full configuration reference](https://docitruby.dev/docs/configuration) for all options including `license`, `contact`, `terms_of_service`, multiple auth schemes, and server URLs.
60
+
61
+ ## Quick Start
106
62
 
107
- Docit supports two styles for documenting endpoints. Choose whichever fits your project or mix both.
63
+ Docit supports two styles. Choose whichever fits your project or mix both.
108
64
 
109
65
  ### Style 1: Inline (simple APIs)
110
66
 
111
- Add `swagger_doc` blocks directly in your controller:
67
+ Add `doc_for` blocks directly in your controller:
112
68
 
113
69
  ```ruby
114
70
  class Api::V1::UsersController < ApplicationController
115
- swagger_doc :index do
71
+ doc_for :index do
116
72
  summary "List all users"
117
73
  tags "Users"
118
74
  response 200, "Users retrieved"
119
75
  end
76
+
120
77
  def index
121
78
  # your code
122
79
  end
123
80
  end
124
81
  ```
125
82
 
83
+ > **Upgrading?** The previous `swagger_doc` method still works as an alias for `doc_for`, so existing code won't break.
84
+
126
85
  ### Style 2: Separate doc files (recommended for larger APIs)
127
86
 
128
87
  Keep controllers clean by defining docs in dedicated files:
@@ -134,387 +93,70 @@ module Api::V1::UsersDocs
134
93
 
135
94
  doc :index do
136
95
  summary "List all users"
137
- description "Returns a paginated list of users"
138
96
  tags "Users"
139
97
 
140
- parameter :page, location: :query, type: :integer, description: "Page number"
141
-
142
98
  response 200, "Users retrieved" do
143
99
  property :users, type: :array, items: :object do
144
100
  property :id, type: :integer, example: 1
145
101
  property :email, type: :string, example: "user@example.com"
146
102
  end
147
- property :total, type: :integer, example: 42
148
- end
149
- end
150
-
151
- doc :create do
152
- summary "Create a user"
153
- tags "Users"
154
-
155
- request_body required: true do
156
- property :email, type: :string, required: true
157
- property :password, type: :string, required: true, format: :password
158
- end
159
-
160
- response 201, "User created" do
161
- property :id, type: :integer
162
- end
163
-
164
- response 422, "Validation failed" do
165
- property :errors, type: :object do
166
- property :email, type: :array, items: :string
167
- end
168
103
  end
169
104
  end
170
105
  end
106
+ ```
171
107
 
172
- # app/controllers/api/v1/users_controller.rb — stays clean!
108
+ ```ruby
109
+ # app/controllers/api/v1/users_controller.rb
173
110
  class Api::V1::UsersController < ApplicationController
174
111
  use_docs Api::V1::UsersDocs
175
112
 
176
113
  def index
177
114
  # pure business logic
178
115
  end
179
-
180
- def create
181
- # pure business logic
182
- end
183
- end
184
- ```
185
-
186
- You can also mix both styles — use `use_docs` for most actions and add inline `swagger_doc` for one-offs:
187
-
188
- ```ruby
189
- class Api::V1::UsersController < ApplicationController
190
- use_docs Api::V1::UsersDocs # loads :index and :create from doc file
191
-
192
- swagger_doc :destroy do # inline doc for this one action
193
- summary "Delete user"
194
- tags "Users"
195
- response 204, "Deleted"
196
- end
197
-
198
- def index; end
199
- def create; end
200
- def destroy; end
201
- end
202
- ```
203
-
204
- ### Endpoint documentation DSL
205
-
206
- The following examples work in both `swagger_doc` blocks and `doc` blocks.
207
-
208
- ### Request bodies
209
-
210
- ```ruby
211
- swagger_doc :create do
212
- summary "Create a user"
213
- tags "Users"
214
-
215
- request_body required: true do
216
- property :email, type: :string, required: true, example: "user@example.com"
217
- property :password, type: :string, required: true, format: :password
218
- property :name, type: :string, example: "Jane Doe"
219
- property :profile, type: :object do
220
- property :bio, type: :string
221
- property :avatar_url, type: :string, format: :uri
222
- end
223
- end
224
-
225
- response 201, "User created" do
226
- property :id, type: :integer, example: 1
227
- property :email, type: :string, example: "user@example.com"
228
- end
229
-
230
- response 422, "Validation failed" do
231
- property :errors, type: :object do
232
- property :email, type: :array, items: :string
233
- end
234
- end
235
- end
236
- def create
237
- # your code
238
- end
239
- ```
240
-
241
- ### Path parameters
242
-
243
- ```ruby
244
- swagger_doc :show do
245
- summary "Get a user"
246
- tags "Users"
247
-
248
- parameter :id, location: :path, type: :integer, required: true, description: "User ID"
249
-
250
- response 200, "User found" do
251
- property :id, type: :integer, example: 1
252
- property :email, type: :string
253
- property :name, type: :string
254
- end
255
-
256
- response 404, "User not found" do
257
- property :error, type: :string, example: "Not found"
258
- end
259
- end
260
- def show
261
- # your code
262
- end
263
- ```
264
-
265
- ### Enums
266
-
267
- ```ruby
268
- swagger_doc :index do
269
- summary "List orders"
270
- tags "Orders"
271
-
272
- parameter :status, location: :query, type: :string,
273
- enum: %w[pending shipped delivered],
274
- description: "Filter by status"
275
-
276
- response 200, "Orders list" do
277
- property :orders, type: :array do
278
- property :id, type: :integer
279
- property :status, type: :string, enum: %w[pending shipped delivered]
280
- end
281
- end
282
- end
283
- ```
284
-
285
- ### Security
286
-
287
- Mark endpoints as requiring authentication:
288
-
289
- ```ruby
290
- swagger_doc :destroy do
291
- summary "Delete a user"
292
- tags "Users"
293
- security :bearer_auth # references the scheme from your config
294
-
295
- response 204, "User deleted"
296
- response 401, "Unauthorized"
297
- end
298
- ```
299
-
300
- ### Deprecated endpoints
301
-
302
- ```ruby
303
- swagger_doc :legacy_search do
304
- summary "Search (legacy)"
305
- tags "Search"
306
- deprecated
307
-
308
- response 200, "Results"
309
- end
310
- ```
311
-
312
- ### Nested objects and arrays
313
-
314
- ```ruby
315
- response 200, "Success" do
316
- property :user, type: :object do
317
- property :id, type: :integer
318
- property :name, type: :string
319
- property :addresses, type: :array do
320
- property :street, type: :string
321
- property :city, type: :string
322
- property :zip, type: :string
323
- end
324
- end
325
- end
326
- ```
327
-
328
- ### Response examples
329
-
330
- ```ruby
331
- response 200, "User found" do
332
- property :id, type: :integer
333
- property :email, type: :string
334
-
335
- example "admin_user",
336
- { id: 1, email: "admin@example.com" },
337
- description: "An admin user"
338
-
339
- example "regular_user",
340
- { id: 2, email: "user@example.com" },
341
- description: "A regular user"
342
- end
343
- ```
344
-
345
- ### Shared schemas (`$ref`)
346
-
347
- Define reusable schemas once and reference them across multiple endpoints:
348
-
349
- ```ruby
350
- # In config/initializers/docit.rb or a dedicated file:
351
- Docit.define_schema :User do
352
- property :id, type: :integer, example: 1
353
- property :email, type: :string, example: "user@example.com"
354
- property :name, type: :string, example: "Jane Doe"
355
- property :address, type: :object do
356
- property :street, type: :string
357
- property :city, type: :string
358
- end
359
- end
360
-
361
- Docit.define_schema :Error do
362
- property :error, type: :string, example: "Not found"
363
- property :details, type: :array, items: :string
364
- end
365
- ```
366
-
367
- Reference them in any endpoint with `schema ref:`:
368
-
369
- ```ruby
370
- swagger_doc :show do
371
- summary "Get user"
372
- tags "Users"
373
-
374
- response 200, "User found" do
375
- schema ref: :User
376
- end
377
-
378
- response 404, "Not found" do
379
- schema ref: :Error
380
- end
381
- end
382
-
383
- swagger_doc :create do
384
- summary "Create user"
385
- tags "Users"
386
-
387
- request_body required: true do
388
- schema ref: :User
389
- end
390
-
391
- response 201, "Created" do
392
- schema ref: :User
393
- end
394
- end
395
- ```
396
-
397
- This outputs `$ref: '#/components/schemas/User'` in the spec — Swagger UI resolves it automatically.
398
-
399
- ### File uploads
400
-
401
- Use `type: :file` with `content_type: "multipart/form-data"` for file upload endpoints:
402
-
403
- ```ruby
404
- swagger_doc :upload_avatar do
405
- summary "Upload avatar"
406
- tags "Users"
407
-
408
- request_body required: true, content_type: "multipart/form-data" do
409
- property :avatar, type: :file, required: true, description: "Avatar image"
410
- property :caption, type: :string
411
- end
412
-
413
- response 201, "Avatar uploaded" do
414
- property :url, type: :string, format: :uri
415
- end
416
116
  end
417
117
  ```
418
118
 
419
- `type: :file` maps to `{ type: "string", format: "binary" }` in the OpenAPI spec.
420
-
421
- ## AI Automatic Documentation
422
-
423
- Docit can generate complete API documentation using AI. This works with OpenAI, Anthropic, or Groq (free tier available).
119
+ See the [full DSL reference](https://docitruby.dev/docs/dsl-reference) for request bodies, path parameters, enums, security, nested objects, file uploads, shared schemas, and more.
424
120
 
425
- ### Quick start (included in install)
121
+ ## AI Documentation
426
122
 
427
- When you run `rails generate docit:install` and choose option 1 (AI automatic docs), everything is set up automatically — provider configuration, doc generation, controller wiring, and tag injection.
123
+ Docit can generate complete API documentation using AI. Works with OpenAI, Anthropic, or Groq (free tier available).
428
124
 
429
- Before the first AI request, Docit warns that your controller source code will be sent to the selected provider and asks for confirmation in interactive terminals.
430
-
431
- ### Standalone commands
432
-
433
- You can also set up AI docs separately:
125
+ When you run `rails generate docit:install` and choose AI automatic docs, everything is set up automatically provider configuration, doc generation, controller wiring, and tag injection.
434
126
 
435
127
  ```bash
436
- # Configure your AI provider (one-time setup)
128
+ # Or set up AI docs separately:
437
129
  rails generate docit:ai_setup
438
-
439
- # Generate docs for all undocumented endpoints
440
130
  rails docit:autodoc
441
131
 
442
- # Generate docs for a specific controller
443
- rails docit:autodoc[Api::V1::UsersController]
444
-
445
- # Preview what would be generated without writing files
132
+ # Preview without writing files:
446
133
  DRY_RUN=1 rails docit:autodoc
447
134
  ```
448
135
 
449
- ### Supported providers
450
-
451
- | Provider | Notes |
452
- |------------|-------|
453
- | OpenAI | Requires API key from platform.openai.com |
454
- | Anthropic | Requires API key from console.anthropic.com |
455
- | Groq | Free tier at console.groq.com |
456
-
457
- All providers automatically retry on rate-limit (429) errors with exponential backoff, so free-tier usage works out of the box.
458
-
459
- AI configuration is stored in `.docit_ai.yml`.
460
- If your app does not have a `.gitignore`, add `.docit_ai.yml` manually.
461
-
462
- ### What the AI generates
463
-
464
- For each undocumented endpoint, Docit:
465
-
466
- 1. Reads the controller source code
467
- 2. Inspects the route (HTTP method, path, parameters)
468
- 3. Writes the generated doc block to `app/docs/`
469
- 4. Injects `use_docs` into the controller
470
- 5. Adds tag descriptions to the initializer
471
-
472
- Do not use AI autodoc on controllers that contain secrets, proprietary business rules, or internal comments you do not want sent to an external provider.
136
+ See the [full AI documentation guide](https://docitruby.dev/docs/ai-documentation) for provider details, safety considerations, and standalone commands.
473
137
 
474
138
  ## Documentation UIs
475
139
 
476
- Docit ships with two documentation UIs, both reading from the same OpenAPI spec:
477
-
478
140
  | Path | UI | Notes |
479
141
  |------|------|-------|
480
142
  | `/api-docs` | Default (Scalar) | Configurable via `config.default_ui` |
481
- | `/api-docs/scalar` | Scalar API Reference | Modern UI with built-in API client, dark mode, code samples |
143
+ | `/api-docs/scalar` | Scalar API Reference | Modern UI with API client, dark mode |
482
144
  | `/api-docs/swagger` | Swagger UI | Classic OpenAPI explorer |
483
145
  | `/api-docs/spec` | Raw JSON | OpenAPI 3.0.3 spec |
484
146
 
485
- Both UIs include a navigation bar to switch between them. Set `config.default_ui = :swagger` to make Swagger the default at `/api-docs`.
486
-
487
- ## How it works
147
+ ## How It Works
488
148
 
489
- 1. `swagger_doc` registers an **Operation** for each controller action in a global **Registry**
149
+ 1. `doc_for` registers an **Operation** for each controller action in a global **Registry**
490
150
  2. When someone visits `/api-docs/spec`, Docit's **SchemaGenerator** combines all registered operations with your Rails routes (via **RouteInspector**) to produce an OpenAPI 3.0.3 JSON document
491
151
  3. The **Engine** serves the configured documentation UI at `/api-docs`, pointing it at the generated spec
492
152
 
493
- The DSL is included in all controllers automatically via a Rails Engine initializer — no manual `include` needed if you're using `ActionController::API` or `ActionController::Base`.
494
-
495
- ## Mounting at a different path
496
-
497
- In `config/routes.rb`:
498
-
499
- ```ruby
500
- mount Docit::Engine => "/docs" # now at /docs instead of /api-docs
501
- ```
502
-
503
- ## JSON spec only
504
-
505
- If you just want the raw OpenAPI JSON (e.g., for code generation):
506
-
507
- ```
508
- GET /api-docs/spec
509
- ```
510
-
511
153
  ## Development
512
154
 
513
155
  ```bash
514
156
  git clone https://github.com/S13G/docit.git
515
157
  cd docit
516
158
  bundle install
517
- bundle exec rspec # run all tests
159
+ bundle exec rspec
518
160
  ```
519
161
 
520
162
  ## Contributing
@@ -62,11 +62,11 @@ module Docit
62
62
  end
63
63
 
64
64
  routes_file = Rails.root.join("config", "routes.rb")
65
- if File.exist?(routes_file) && !File.read(routes_file).include?("Docit::Engine")
66
- @output.puts "Warning: Docit engine is not mounted in config/routes.rb"
67
- @output.puts " Run: rails generate docit:install (or add: mount Docit::Engine => \"/api-docs\")"
68
- @output.puts ""
69
- end
65
+ return unless File.exist?(routes_file) && !File.read(routes_file).include?("Docit::Engine")
66
+
67
+ @output.puts "Warning: Docit engine is not mounted in config/routes.rb"
68
+ @output.puts " Run: rails generate docit:install (or add: mount Docit::Engine => \"/api-docs\")"
69
+ @output.puts ""
70
70
  end
71
71
 
72
72
  def detect_gaps
@@ -84,20 +84,20 @@ module Docit
84
84
  @output.puts "Docit will send controller source code to #{config.provider.capitalize} to generate documentation."
85
85
  @output.puts "Review the endpoints first if they contain secrets or proprietary logic."
86
86
 
87
- if @input.respond_to?(:tty?) && @input.tty?
88
- loop do
89
- @output.print "Continue? (y/n): "
90
- choice = @input.gets.to_s.strip.downcase
91
-
92
- case choice
93
- when "y", "yes"
94
- @output.puts ""
95
- return
96
- when "n", "no"
97
- raise Docit::Error, "Autodoc cancelled."
98
- else
99
- @output.puts "Please enter y or n."
100
- end
87
+ return unless @input.respond_to?(:tty?) && @input.tty?
88
+
89
+ loop do
90
+ @output.print "Continue? (y/n): "
91
+ choice = @input.gets.to_s.strip.downcase
92
+
93
+ case choice
94
+ when "y", "yes"
95
+ @output.puts ""
96
+ return
97
+ when "n", "no"
98
+ raise Docit::Error, "Autodoc cancelled."
99
+ else
100
+ @output.puts "Please enter y or n."
101
101
  end
102
102
  end
103
103
  end
@@ -138,6 +138,7 @@ module Docit
138
138
  invalid_output_retries += 1
139
139
  if invalid_output_retries <= MAX_INVALID_OUTPUT_RETRIES
140
140
  validation_error = e.message
141
+ @output.puts " invalid output, retrying"
141
142
  retry
142
143
  end
143
144
 
@@ -184,11 +185,11 @@ module Docit
184
185
 
185
186
  def inject_tags(generated)
186
187
  all_tags = generated.values.flatten.join("\n").scan(/tags\s+["']([^"']+)["']/).flatten
187
- if all_tags.any?
188
- injected = TagInjector.new(tags: all_tags).inject
189
- injected.each { |tag| @output.puts " Added tag \"#{tag}\" to config/initializers/docit.rb" }
190
- @results[:tags] = injected
191
- end
188
+ return unless all_tags.any?
189
+
190
+ injected = TagInjector.new(tags: all_tags).inject
191
+ injected.each { |tag| @output.puts " Added tag \"#{tag}\" to config/initializers/docit.rb" }
192
+ @results[:tags] = injected
192
193
  end
193
194
 
194
195
  def strip_markdown_fences(text)
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docit
4
+ module Ai
5
+ class InvalidDocBlockError < Error; end
6
+
7
+ class DocBlockValidator
8
+ def initialize(controller:, action:, doc_block:)
9
+ @controller = controller
10
+ @action = action.to_sym
11
+ @doc_block = doc_block
12
+ end
13
+
14
+ def validate!
15
+ doc_module = Module.new
16
+ doc_module.extend(Docit::DocFile)
17
+ doc_module.module_eval(@doc_block, "(generated Docit block)", 1)
18
+
19
+ validate_actions!(doc_module.actions)
20
+
21
+ operation = Docit::Operation.new(controller: @controller, action: @action)
22
+ operation.instance_eval(&doc_module[@action])
23
+
24
+ true
25
+ rescue SyntaxError, StandardError => e
26
+ raise InvalidDocBlockError, error_message_for(e)
27
+ end
28
+
29
+ private
30
+
31
+ def validate_actions!(actions)
32
+ return if actions == [@action]
33
+
34
+ raise InvalidDocBlockError, "Generated output did not define a doc block" if actions.empty?
35
+
36
+ action_list = actions.map { |action| ":#{action}" }.join(", ")
37
+ raise InvalidDocBlockError,
38
+ "Generated output must define only doc :#{@action}, got #{action_list}"
39
+ end
40
+
41
+ def error_message_for(error)
42
+ return error.message if error.is_a?(InvalidDocBlockError)
43
+
44
+ "#{error.class}: #{error.message}"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -8,7 +8,7 @@ module Docit
8
8
  end
9
9
 
10
10
  def inject
11
- return [] if initializer_path && File.exist?(initializer_path) == false
11
+ return [] unless initializer_path && File.exist?(initializer_path)
12
12
 
13
13
  content = File.read(initializer_path)
14
14
  existing_tags = content.scan(/config\.tag\s+["']([^"']+)["']/).flatten
@@ -17,6 +17,9 @@ module Docit
17
17
  @security_schemes = {}
18
18
  @tags = []
19
19
  @servers = []
20
+ @license = nil
21
+ @contact = nil
22
+ @terms_of_service = nil
20
23
  end
21
24
 
22
25
  def default_ui=(value)
@@ -75,5 +78,35 @@ module Docit
75
78
  def servers
76
79
  @servers.dup
77
80
  end
81
+
82
+ def license(name:, url: nil)
83
+ entry = { name: name }
84
+ entry[:url] = url if url
85
+ @license = entry
86
+ end
87
+
88
+ def license_info
89
+ @license&.dup
90
+ end
91
+
92
+ def contact(name: nil, email: nil, url: nil)
93
+ entry = {}
94
+ entry[:name] = name if name
95
+ entry[:email] = email if email
96
+ entry[:url] = url if url
97
+ @contact = entry
98
+ end
99
+
100
+ def contact_info
101
+ @contact&.dup
102
+ end
103
+
104
+ def terms_of_service(url)
105
+ @terms_of_service = url
106
+ end
107
+
108
+ def terms_of_service_url
109
+ @terms_of_service
110
+ end
78
111
  end
79
112
  end
@@ -40,7 +40,7 @@ module Docit
40
40
  base.instance_variable_set(:@_docs, {})
41
41
  end
42
42
 
43
- # The block receives the same DSL as swagger_doc.
43
+ # The block receives the same DSL as doc_for.
44
44
  def doc(action, &block)
45
45
  @_docs[action.to_sym] = block
46
46
  end
data/lib/docit/dsl.rb CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  module Docit
4
4
  # Included in all Rails controllers via the Engine.
5
- # Provides +swagger_doc+ and +use_docs+ class methods.
5
+ # Provides +doc_for+ and +use_docs+ class methods.
6
6
  module DSL
7
7
  def self.included(base)
8
8
  base.extend(ClassMethods)
9
9
  end
10
10
 
11
11
  module ClassMethods
12
- def swagger_doc(action, &block)
12
+ def doc_for(action, &block)
13
13
  operation = Operation.new(
14
14
  controller: name,
15
15
  action: action
@@ -18,9 +18,12 @@ module Docit
18
18
  Registry.register(operation)
19
19
  end
20
20
 
21
+ # Backward-compatible alias
22
+ alias swagger_doc doc_for
23
+
21
24
  def use_docs(doc_module)
22
25
  doc_module.actions.each do |action|
23
- swagger_doc(action, &doc_module[action])
26
+ doc_for(action, &doc_module[action])
24
27
  end
25
28
  end
26
29
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  module Docit
4
4
  # Represents the documentation for a single controller action.
5
- # Created by +swagger_doc+ and stored in the {Registry}.
5
+ # Created by +doc_for+ and stored in the {Registry}.
6
6
  class Operation
7
7
  attr_reader :controller, :action, :_summary, :_description,
8
8
  :_tags, :_responses, :_request_body, :_parameters,
9
- :_security, :_deprecated
9
+ :_security, :_deprecated, :_operation_id
10
10
 
11
11
  def initialize(controller:, action:)
12
12
  @controller = controller
@@ -17,6 +17,7 @@ module Docit
17
17
  @_request_body = nil
18
18
  @_security = []
19
19
  @_deprecated = false
20
+ @_operation_id = nil
20
21
  end
21
22
 
22
23
  def summary(text)
@@ -35,6 +36,10 @@ module Docit
35
36
  @_deprecated = value
36
37
  end
37
38
 
39
+ def operation_id(text)
40
+ @_operation_id = text
41
+ end
42
+
38
43
  def security(scheme)
39
44
  @_security << scheme
40
45
  end
@@ -5,7 +5,7 @@ module Docit
5
5
  class RouteInspector
6
6
  VALID_METHODS = %w[get post put patch delete].freeze
7
7
 
8
- # Eagerly loads controller classes so swagger_doc/use_docs macros run before spec generation.
8
+ # Eagerly loads controller classes so doc_for/use_docs macros run before spec generation.
9
9
  def self.eager_load_controllers!
10
10
  return if defined?(Rails).nil? || Rails.application.routes.nil?
11
11
 
@@ -12,11 +12,7 @@ module Docit
12
12
 
13
13
  spec = {
14
14
  openapi: "3.0.3",
15
- info: {
16
- title: config.title,
17
- version: config.version,
18
- description: config.description
19
- },
15
+ info: build_info(config),
20
16
  paths: build_paths,
21
17
  components: {
22
18
  securitySchemes: config.security_schemes
@@ -37,6 +33,18 @@ module Docit
37
33
 
38
34
  private
39
35
 
36
+ def build_info(config)
37
+ info = {
38
+ title: config.title,
39
+ version: config.version,
40
+ description: config.description
41
+ }
42
+ info[:license] = config.license_info if config.license_info
43
+ info[:contact] = config.contact_info if config.contact_info
44
+ info[:termsOfService] = config.terms_of_service_url if config.terms_of_service_url
45
+ info
46
+ end
47
+
40
48
  def build_paths
41
49
  paths = {}
42
50
 
@@ -58,6 +66,7 @@ module Docit
58
66
 
59
67
  def build_operation(operation)
60
68
  result = {
69
+ operationId: operation._operation_id || generate_operation_id(operation),
61
70
  summary: operation._summary || operation.action.humanize,
62
71
  description: operation._description || "",
63
72
  tags: operation._tags,
@@ -183,5 +192,15 @@ module Docit
183
192
  hash[ex[:name].to_s] = entry
184
193
  end
185
194
  end
195
+
196
+ def generate_operation_id(operation)
197
+ # "Api::V1::UsersController" → "users" ; "index" → "listUsers"
198
+ resource = operation.controller
199
+ .gsub(/.*::/, "") # strip namespace
200
+ .gsub(/Controller$/, "") # strip suffix
201
+ action = operation.action
202
+
203
+ "#{action}_#{resource}".gsub("::", "_").downcase
204
+ end
186
205
  end
187
206
  end
data/lib/docit/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Docit
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -102,7 +102,7 @@ module Docit
102
102
  say ""
103
103
  say "Next steps:"
104
104
  say " 1. Edit config/initializers/docit.rb to customize your API docs"
105
- say " 2. Add swagger_doc blocks or create doc files under app/docs/"
105
+ say " 2. Add doc_for blocks or create doc files under app/docs/"
106
106
  say " 3. Visit /api-docs to see your Swagger UI"
107
107
  say ""
108
108
  say "You can set up docs later with:"
@@ -146,7 +146,7 @@ module Docit
146
146
 
147
147
  def update_gitignore
148
148
  gitignore = Rails.root.join(".gitignore")
149
- if !(File.exist?(gitignore))
149
+ unless File.exist?(gitignore)
150
150
  say "Warning: .gitignore not found. Add .docit_ai.yml manually to avoid committing your API key.", :yellow
151
151
  return
152
152
  end
@@ -167,7 +167,7 @@ module Docit
167
167
  choice = ask(prompt).to_s.strip
168
168
  return choice if choices.include?(choice)
169
169
 
170
- say "Invalid choice. Please enter #{choices.join(', ')}.", :red
170
+ say "Invalid choice. Please enter #{choices.join(", ")}.", :red
171
171
  end
172
172
  end
173
173
 
data/lib/tasks/docit.rake CHANGED
@@ -3,7 +3,7 @@
3
3
  namespace :docit do
4
4
  desc "Generate documentation for undocumented API endpoints using AI"
5
5
  task :autodoc, [:controller] => :environment do |_t, args|
6
- dry_run = ENV.fetch("DRY_RUN", "0") == "1" || ARGV.include?("--dry-run")
6
+ dry_run = ENV.fetch("DRY_RUN", "0") == "1"
7
7
  controller_filter = args[:controller]
8
8
 
9
9
  runner = Docit::Ai::AutodocRunner.new(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - S13G
@@ -49,6 +49,7 @@ files:
49
49
  - lib/docit/ai/autodoc_runner.rb
50
50
  - lib/docit/ai/client.rb
51
51
  - lib/docit/ai/configuration.rb
52
+ - lib/docit/ai/doc_block_validator.rb
52
53
  - lib/docit/ai/doc_writer.rb
53
54
  - lib/docit/ai/gap_detector.rb
54
55
  - lib/docit/ai/groq_client.rb
@@ -77,14 +78,14 @@ files:
77
78
  - lib/generators/docit/install/templates/initializer.rb
78
79
  - lib/tasks/docit.rake
79
80
  - sig/docit.rbs
80
- homepage: https://github.com/S13G/docit
81
+ homepage: https://docitruby.dev
81
82
  licenses:
82
83
  - MIT
83
84
  metadata:
84
- homepage_uri: https://github.com/S13G/docit
85
+ homepage_uri: https://docitruby.dev
85
86
  source_code_uri: https://github.com/S13G/docit
86
- changelog_uri: https://github.com/S13G/docit/blob/main/CHANGELOG.md
87
- documentation_uri: https://rubydoc.info/gems/docit
87
+ changelog_uri: https://github.com/S13G/docit/blob/master/CHANGELOG.md
88
+ documentation_uri: https://docitruby.dev/docs
88
89
  bug_tracker_uri: https://github.com/S13G/docit/issues
89
90
  rubygems_mfa_required: 'true'
90
91
  rdoc_options: []