docit 0.2.1 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +28 -5
- data/app/controllers/docit/ui_controller.rb +25 -48
- data/config/routes.rb +2 -0
- data/docs/images/scalar_image.png +0 -0
- data/docs/images/swagger_image.png +0 -0
- data/lib/docit/ai/autodoc_runner.rb +38 -22
- data/lib/docit/ai/configuration.rb +9 -2
- data/lib/docit/ai/doc_block_validator.rb +48 -0
- data/lib/docit/ai/prompt_builder.rb +21 -3
- data/lib/docit/ai.rb +1 -0
- data/lib/docit/configuration.rb +14 -1
- data/lib/docit/ui/base_renderer.rb +55 -0
- data/lib/docit/ui/scalar_renderer.rb +38 -0
- data/lib/docit/ui/swagger_renderer.rb +54 -0
- data/lib/docit/version.rb +1 -1
- data/lib/docit.rb +3 -0
- data/lib/generators/docit/install/templates/initializer.rb +5 -2
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ac8acf7fd2b736bb2c89c63a63bca482239783457d7572d864e0257a3a2c6cd
|
|
4
|
+
data.tar.gz: 1605b07b832dc73f4e7f00e0840aa72267cbb60262e17baba6842134ee576e19
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a72fa34e950fdf918daab680c287311401a4059fa3b8d0bf16a0ebcef210ed607b697d3ad58a3343bf7d105a2a11d717efd0f8ee239464a82f58ca5f934b1e79
|
|
7
|
+
data.tar.gz: 96516820d968faae672a4caa7b6c52c9d79356b6366d09edaa019badb2c6949c0b4087115e6f306f559904b5dbcb02b594c2e0e58e864458c0c460e381d4d894
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-04-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Scalar API Reference as a second documentation UI alongside Swagger UI
|
|
7
|
+
- New routes: `/api-docs/scalar` and `/api-docs/swagger` for direct access to each UI
|
|
8
|
+
- `config.default_ui` option (`:scalar` or `:swagger`) to control which UI renders at the root `/api-docs` path
|
|
9
|
+
- Navigation bar across both UIs for one-click switching between Scalar and Swagger
|
|
10
|
+
- Modular renderer architecture (`Docit::UI::BaseRenderer`, `SwaggerRenderer`, `ScalarRenderer`) for easy extension
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Default documentation UI is now Scalar (previously Swagger UI)
|
|
14
|
+
- `config.description` now defaults to a welcome message instead of an empty string
|
|
15
|
+
- Install generator template now documents the `default_ui` option
|
|
16
|
+
- UI controller refactored from monolithic HTML generation to thin dispatcher with pluggable renderers
|
|
17
|
+
|
|
3
18
|
## [0.2.1] - 2026-04-11
|
|
4
19
|
|
|
5
20
|
### Fixed
|
data/README.md
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
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
|
+
### Scalar (default)
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
### Swagger
|
|
12
|
+

|
|
13
|
+
|
|
8
14
|
## Table Of Contents
|
|
9
15
|
|
|
10
16
|
- Getting started
|
|
@@ -32,6 +38,7 @@ Decorator-style API documentation for Ruby on Rails. Write OpenAPI 3.0.3 docs wi
|
|
|
32
38
|
- [Supported providers](#supported-providers)
|
|
33
39
|
- [What the AI generates](#what-the-ai-generates)
|
|
34
40
|
- Runtime and development
|
|
41
|
+
- [Documentation UIs](#documentation-uis)
|
|
35
42
|
- [How it works](#how-it-works)
|
|
36
43
|
- [Mounting at a different path](#mounting-at-a-different-path)
|
|
37
44
|
- [JSON spec only](#json-spec-only)
|
|
@@ -61,13 +68,13 @@ rails generate docit:install
|
|
|
61
68
|
The install generator does everything in one step:
|
|
62
69
|
|
|
63
70
|
1. Creates `config/initializers/docit.rb` with default settings
|
|
64
|
-
2. Mounts the
|
|
71
|
+
2. Mounts the documentation engine at `/api-docs` in your routes
|
|
65
72
|
3. Asks how you'd like to set up your docs:
|
|
66
73
|
- **AI automatic docs** — configure an AI provider, then Docit scans your routes and generates complete documentation for every endpoint
|
|
67
74
|
- **Manual docs** — Docit scans your routes and creates scaffolded doc files with TODO placeholders, injects `use_docs` into controllers, and lets you fill in the details
|
|
68
75
|
- **Skip** — just install the base config and set up docs later
|
|
69
76
|
|
|
70
|
-
Visit `/api-docs` to see your interactive API documentation.
|
|
77
|
+
Visit `/api-docs` to see your interactive API documentation (Scalar by default, Swagger UI also available at `/api-docs/swagger`).
|
|
71
78
|
|
|
72
79
|
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.
|
|
73
80
|
|
|
@@ -81,17 +88,20 @@ Docit.configure do |config|
|
|
|
81
88
|
config.version = "1.0.0"
|
|
82
89
|
config.description = "Backend API documentation"
|
|
83
90
|
|
|
91
|
+
# Documentation UI: :scalar (default) or :swagger
|
|
92
|
+
config.default_ui = :scalar
|
|
93
|
+
|
|
84
94
|
# Authentication: pick one (or multiple):
|
|
85
95
|
config.auth :bearer # Bearer token (JWT by default)
|
|
86
96
|
config.auth :basic # HTTP Basic
|
|
87
97
|
config.auth :api_key, name: "X-API-Key", # API key in header
|
|
88
98
|
location: "header"
|
|
89
99
|
|
|
90
|
-
# Tag descriptions (shown in
|
|
100
|
+
# Tag descriptions (shown in the documentation sidebar):
|
|
91
101
|
config.tag "Users", description: "User account management"
|
|
92
102
|
config.tag "Auth", description: "Authentication endpoints"
|
|
93
103
|
|
|
94
|
-
# Server URLs (shown in
|
|
104
|
+
# Server URLs (shown in the server dropdown):
|
|
95
105
|
config.server "https://api.example.com", description: "Production"
|
|
96
106
|
config.server "https://staging.example.com", description: "Staging"
|
|
97
107
|
config.server "http://localhost:3000", description: "Development"
|
|
@@ -467,11 +477,24 @@ For each undocumented endpoint, Docit:
|
|
|
467
477
|
|
|
468
478
|
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.
|
|
469
479
|
|
|
480
|
+
## Documentation UIs
|
|
481
|
+
|
|
482
|
+
Docit ships with two documentation UIs, both reading from the same OpenAPI spec:
|
|
483
|
+
|
|
484
|
+
| Path | UI | Notes |
|
|
485
|
+
|------|------|-------|
|
|
486
|
+
| `/api-docs` | Default (Scalar) | Configurable via `config.default_ui` |
|
|
487
|
+
| `/api-docs/scalar` | Scalar API Reference | Modern UI with built-in API client, dark mode, code samples |
|
|
488
|
+
| `/api-docs/swagger` | Swagger UI | Classic OpenAPI explorer |
|
|
489
|
+
| `/api-docs/spec` | Raw JSON | OpenAPI 3.0.3 spec |
|
|
490
|
+
|
|
491
|
+
Both UIs include a navigation bar to switch between them. Set `config.default_ui = :swagger` to make Swagger the default at `/api-docs`.
|
|
492
|
+
|
|
470
493
|
## How it works
|
|
471
494
|
|
|
472
495
|
1. `swagger_doc` registers an **Operation** for each controller action in a global **Registry**
|
|
473
496
|
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
|
|
474
|
-
3. The **Engine** serves
|
|
497
|
+
3. The **Engine** serves the configured documentation UI at `/api-docs`, pointing it at the generated spec
|
|
475
498
|
|
|
476
499
|
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`.
|
|
477
500
|
|
|
@@ -4,10 +4,16 @@ require "json"
|
|
|
4
4
|
|
|
5
5
|
module Docit
|
|
6
6
|
class UiController < ActionController::Base
|
|
7
|
-
SWAGGER_UI_VERSION = "5.32.2"
|
|
8
|
-
|
|
9
7
|
def index
|
|
10
|
-
|
|
8
|
+
render_ui(Docit.configuration.default_ui)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def swagger
|
|
12
|
+
render_ui(:swagger)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def scalar
|
|
16
|
+
render_ui(:scalar)
|
|
11
17
|
end
|
|
12
18
|
|
|
13
19
|
def spec
|
|
@@ -17,52 +23,23 @@ module Docit
|
|
|
17
23
|
|
|
18
24
|
private
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
RENDERERS = {
|
|
27
|
+
swagger: UI::SwaggerRenderer,
|
|
28
|
+
scalar: UI::ScalarRenderer
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
def render_ui(ui_name)
|
|
32
|
+
renderer = RENDERERS.fetch(ui_name).new(spec_url: spec_url, nav_paths: nav_paths)
|
|
33
|
+
render html: renderer.render.html_safe, layout: false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def spec_url
|
|
37
|
+
"#{request.base_url}#{Docit::Engine.routes.url_helpers.spec_path}"
|
|
38
|
+
end
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<head>
|
|
29
|
-
<meta charset="UTF-8">
|
|
30
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
31
|
-
<title>#{escaped_title}</title>
|
|
32
|
-
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@#{SWAGGER_UI_VERSION}/swagger-ui.css" />
|
|
33
|
-
<style>
|
|
34
|
-
html { box-sizing: border-box; overflow-y: scroll; }
|
|
35
|
-
*, *:before, *:after { box-sizing: inherit; }
|
|
36
|
-
body { margin: 0; background: #fafafa; }
|
|
37
|
-
</style>
|
|
38
|
-
</head>
|
|
39
|
-
<body>
|
|
40
|
-
<div id="swagger-ui"></div>
|
|
41
|
-
<script src="https://unpkg.com/swagger-ui-dist@#{SWAGGER_UI_VERSION}/swagger-ui-bundle.js"></script>
|
|
42
|
-
<script>
|
|
43
|
-
SwaggerUIBundle({
|
|
44
|
-
url: #{spec_url_json},
|
|
45
|
-
dom_id: '#swagger-ui',
|
|
46
|
-
presets: [
|
|
47
|
-
SwaggerUIBundle.presets.apis,
|
|
48
|
-
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
49
|
-
],
|
|
50
|
-
layout: "BaseLayout",
|
|
51
|
-
deepLinking: true,
|
|
52
|
-
showExtensions: true,
|
|
53
|
-
showCommonExtensions: true,
|
|
54
|
-
requestInterceptor: function(req) {
|
|
55
|
-
var url = new URL(req.url);
|
|
56
|
-
url.protocol = window.location.protocol;
|
|
57
|
-
url.host = window.location.host;
|
|
58
|
-
req.url = url.toString();
|
|
59
|
-
return req;
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
</script>
|
|
63
|
-
</body>
|
|
64
|
-
</html>
|
|
65
|
-
HTML
|
|
40
|
+
def nav_paths
|
|
41
|
+
helpers = Docit::Engine.routes.url_helpers
|
|
42
|
+
{ swagger: helpers.swagger_path, scalar: helpers.scalar_path }
|
|
66
43
|
end
|
|
67
44
|
end
|
|
68
45
|
end
|
data/config/routes.rb
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Docit
|
|
4
4
|
module Ai
|
|
5
5
|
class AutodocRunner
|
|
6
|
+
MAX_INVALID_OUTPUT_RETRIES = 2
|
|
7
|
+
|
|
6
8
|
attr_reader :results
|
|
7
9
|
|
|
8
10
|
def initialize(controller_filter: nil, dry_run: false, input: $stdin, output: $stdout)
|
|
@@ -50,7 +52,7 @@ module Docit
|
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
def check_base_setup!
|
|
53
|
-
if
|
|
55
|
+
if defined?(Rails) == false || Rails.respond_to?(:root) == false || Rails.root.nil?
|
|
54
56
|
raise Docit::Error, "Docit requires a Rails application. Run this command from your app root."
|
|
55
57
|
end
|
|
56
58
|
|
|
@@ -82,20 +84,20 @@ module Docit
|
|
|
82
84
|
@output.puts "Docit will send controller source code to #{config.provider.capitalize} to generate documentation."
|
|
83
85
|
@output.puts "Review the endpoints first if they contain secrets or proprietary logic."
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
99
101
|
end
|
|
100
102
|
end
|
|
101
103
|
end
|
|
@@ -115,17 +117,31 @@ module Docit
|
|
|
115
117
|
@output.puts " skipped (controller source file not found)"
|
|
116
118
|
next
|
|
117
119
|
end
|
|
118
|
-
|
|
119
|
-
prompt = builder.build
|
|
120
120
|
retries = 0
|
|
121
|
+
invalid_output_retries = 0
|
|
122
|
+
validation_error = nil
|
|
121
123
|
|
|
122
124
|
begin
|
|
125
|
+
prompt = builder.build(validation_error: validation_error)
|
|
123
126
|
doc_block = client.generate(prompt).strip
|
|
124
127
|
doc_block = strip_markdown_fences(doc_block)
|
|
128
|
+
DocBlockValidator.new(
|
|
129
|
+
controller: gap[:controller],
|
|
130
|
+
action: gap[:action],
|
|
131
|
+
doc_block: doc_block
|
|
132
|
+
).validate!
|
|
125
133
|
|
|
126
134
|
generated[gap[:controller]] << doc_block
|
|
127
135
|
@results[:generated] += 1
|
|
128
136
|
@output.puts " done"
|
|
137
|
+
rescue Docit::Ai::InvalidDocBlockError => e
|
|
138
|
+
invalid_output_retries += 1
|
|
139
|
+
if invalid_output_retries <= MAX_INVALID_OUTPUT_RETRIES
|
|
140
|
+
validation_error = e.message
|
|
141
|
+
retry
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
@output.puts " failed (invalid doc DSL: #{e.message})"
|
|
129
145
|
rescue Docit::Ai::RateLimitError => e
|
|
130
146
|
retries += 1
|
|
131
147
|
if retries <= max_retries
|
|
@@ -168,11 +184,11 @@ module Docit
|
|
|
168
184
|
|
|
169
185
|
def inject_tags(generated)
|
|
170
186
|
all_tags = generated.values.flatten.join("\n").scan(/tags\s+["']([^"']+)["']/).flatten
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
176
192
|
end
|
|
177
193
|
|
|
178
194
|
def strip_markdown_fences(text)
|
|
@@ -6,6 +6,11 @@ module Docit
|
|
|
6
6
|
module Ai
|
|
7
7
|
class Configuration
|
|
8
8
|
CONFIG_FILE = ".docit_ai.yml"
|
|
9
|
+
GENERATED_FILE_COMMENT = <<~TEXT
|
|
10
|
+
# If you want to change the model and start Docit setup again,
|
|
11
|
+
# rerun: rails generate docit:install
|
|
12
|
+
|
|
13
|
+
TEXT
|
|
9
14
|
|
|
10
15
|
PROVIDERS = %w[openai anthropic groq].freeze
|
|
11
16
|
|
|
@@ -51,11 +56,13 @@ module Docit
|
|
|
51
56
|
config = new(provider: provider, model: model, api_key: api_key)
|
|
52
57
|
raise Error, "Invalid configuration" if config.valid? == false
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
yaml = {
|
|
55
60
|
"provider" => config.provider,
|
|
56
61
|
"model" => config.model,
|
|
57
62
|
"api_key" => config.api_key
|
|
58
|
-
}.to_yaml
|
|
63
|
+
}.to_yaml
|
|
64
|
+
|
|
65
|
+
File.write(config_path, GENERATED_FILE_COMMENT + yaml)
|
|
59
66
|
File.chmod(0o600, config_path)
|
|
60
67
|
|
|
61
68
|
config
|
|
@@ -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
|
|
@@ -66,7 +66,7 @@ module Docit
|
|
|
66
66
|
@gap = gap
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
def build
|
|
69
|
+
def build(validation_error: nil)
|
|
70
70
|
<<~PROMPT
|
|
71
71
|
You are generating Docit DSL documentation for a Ruby on Rails API endpoint.
|
|
72
72
|
|
|
@@ -89,12 +89,18 @@ module Docit
|
|
|
89
89
|
Rules:
|
|
90
90
|
- Output ONLY the `doc :#{@gap[:action]} do ... end` block
|
|
91
91
|
- No module wrapper, no explanation, no markdown fences
|
|
92
|
+
- Use exactly `doc :#{@gap[:action]} do ... end` for the requested action
|
|
93
|
+
- Use ONLY the DSL methods listed above
|
|
92
94
|
- Infer parameters from the path (e.g., {id} → path parameter)
|
|
93
95
|
- Infer request body from params usage in the controller
|
|
94
96
|
- Infer response structure from render calls
|
|
97
|
+
- Inside `request_body` and `response` blocks, define nested data only with `property ..., type: :object` or `property ..., type: :array`
|
|
98
|
+
- Never call standalone helpers such as `object`, `array`, `string`, `integer`, `number`, or `boolean`
|
|
99
|
+
- Return valid Ruby that can be `instance_eval`'d as-is
|
|
95
100
|
- Use realistic examples
|
|
96
101
|
- Include appropriate error responses
|
|
97
102
|
- Use the controller name to determine appropriate tags
|
|
103
|
+
#{validation_feedback(validation_error)}
|
|
98
104
|
PROMPT
|
|
99
105
|
end
|
|
100
106
|
|
|
@@ -114,10 +120,22 @@ module Docit
|
|
|
114
120
|
end
|
|
115
121
|
|
|
116
122
|
def controller_file_path
|
|
117
|
-
return nil
|
|
123
|
+
return nil unless defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root
|
|
118
124
|
|
|
119
125
|
relative = @gap[:controller].underscore
|
|
120
|
-
Rails.root.join("app", "controllers", "#{relative}.rb").to_s
|
|
126
|
+
::Rails.root.join("app", "controllers", "#{relative}.rb").to_s
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def validation_feedback(validation_error)
|
|
130
|
+
return "" if validation_error.nil? || validation_error.empty?
|
|
131
|
+
|
|
132
|
+
<<~FEEDBACK
|
|
133
|
+
|
|
134
|
+
Previous attempt failed Docit validation:
|
|
135
|
+
- #{validation_error}
|
|
136
|
+
|
|
137
|
+
Regenerate the block and fix that error.
|
|
138
|
+
FEEDBACK
|
|
121
139
|
end
|
|
122
140
|
end
|
|
123
141
|
end
|
data/lib/docit/ai.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "ai/openai_client"
|
|
|
6
6
|
require_relative "ai/anthropic_client"
|
|
7
7
|
require_relative "ai/groq_client"
|
|
8
8
|
require_relative "ai/gap_detector"
|
|
9
|
+
require_relative "ai/doc_block_validator"
|
|
9
10
|
require_relative "ai/prompt_builder"
|
|
10
11
|
require_relative "ai/doc_writer"
|
|
11
12
|
require_relative "ai/tag_injector"
|
data/lib/docit/configuration.rb
CHANGED
|
@@ -3,18 +3,31 @@
|
|
|
3
3
|
module Docit
|
|
4
4
|
# Holds global API documentation settings: metadata, authentication, tags, and servers.
|
|
5
5
|
class Configuration
|
|
6
|
+
SUPPORTED_UIS = %i[scalar swagger].freeze
|
|
7
|
+
|
|
6
8
|
attr_accessor :title, :version, :description, :base_url
|
|
9
|
+
attr_reader :default_ui
|
|
7
10
|
|
|
8
11
|
def initialize
|
|
9
12
|
@title = "API Documentation"
|
|
10
13
|
@version = "1.0.0"
|
|
11
|
-
@description = ""
|
|
14
|
+
@description = "Welcome to the API documentation. Browse the endpoints below to get started."
|
|
12
15
|
@base_url = "/"
|
|
16
|
+
@default_ui = :scalar
|
|
13
17
|
@security_schemes = {}
|
|
14
18
|
@tags = []
|
|
15
19
|
@servers = []
|
|
16
20
|
end
|
|
17
21
|
|
|
22
|
+
def default_ui=(value)
|
|
23
|
+
ui = value.to_sym
|
|
24
|
+
unless SUPPORTED_UIS.include?(ui)
|
|
25
|
+
raise ArgumentError, "Unsupported UI: #{value}. Must be one of: #{SUPPORTED_UIS.join(", ")}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@default_ui = ui
|
|
29
|
+
end
|
|
30
|
+
|
|
18
31
|
def auth(type, **options)
|
|
19
32
|
case type.to_s.downcase
|
|
20
33
|
when "basic"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
module UI
|
|
5
|
+
class BaseRenderer
|
|
6
|
+
attr_reader :spec_url, :title, :nav_paths
|
|
7
|
+
|
|
8
|
+
def initialize(spec_url:, nav_paths: {})
|
|
9
|
+
@spec_url = spec_url
|
|
10
|
+
@nav_paths = nav_paths
|
|
11
|
+
@title = ERB::Util.html_escape(Docit.configuration.title)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def render
|
|
15
|
+
raise NotImplementedError, "#{self.class}#render must be implemented"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def nav_bar(active:)
|
|
21
|
+
swagger_active = active == :swagger
|
|
22
|
+
scalar_active = active == :scalar
|
|
23
|
+
|
|
24
|
+
<<~HTML
|
|
25
|
+
<nav style="
|
|
26
|
+
display: flex; align-items: center; gap: 8px;
|
|
27
|
+
padding: 6px 16px;
|
|
28
|
+
background: #1a1a2e; color: #fff;
|
|
29
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
30
|
+
font-size: 13px; position: sticky; top: 0; z-index: 9999;
|
|
31
|
+
">
|
|
32
|
+
<span style="font-weight: 600; margin-right: auto;">#{title}</span>
|
|
33
|
+
#{nav_link("Swagger", nav_paths[:swagger], active: swagger_active)}
|
|
34
|
+
#{nav_link("Scalar", nav_paths[:scalar], active: scalar_active)}
|
|
35
|
+
</nav>
|
|
36
|
+
HTML
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def nav_link(label, path, active:)
|
|
40
|
+
escaped_path = ERB::Util.html_escape(path)
|
|
41
|
+
style = if active
|
|
42
|
+
"color: #fff; text-decoration: none; padding: 4px 12px; border-radius: 4px; background: rgba(255,255,255,0.15); font-weight: 500;"
|
|
43
|
+
else
|
|
44
|
+
"color: rgba(255,255,255,0.7); text-decoration: none; padding: 4px 12px; border-radius: 4px;"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
%(<a href="#{escaped_path}" style="#{style}">#{label}</a>)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def spec_url_json
|
|
51
|
+
JSON.generate(spec_url)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
module UI
|
|
5
|
+
class ScalarRenderer < BaseRenderer
|
|
6
|
+
def render
|
|
7
|
+
<<~HTML
|
|
8
|
+
<!DOCTYPE html>
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="UTF-8">
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
13
|
+
<title>#{title}</title>
|
|
14
|
+
<style>
|
|
15
|
+
body { margin: 0; }
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
#{nav_bar(active: :scalar)}
|
|
20
|
+
<script id="api-reference"></script>
|
|
21
|
+
<script>
|
|
22
|
+
document.getElementById('api-reference').dataset.configuration = JSON.stringify({
|
|
23
|
+
spec: { url: #{spec_url_json} },
|
|
24
|
+
theme: "elysiajs",
|
|
25
|
+
showSidebar: true,
|
|
26
|
+
hideDownloadButton: false,
|
|
27
|
+
hideModels: false,
|
|
28
|
+
searchHotKey: "k"
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
HTML
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
module UI
|
|
5
|
+
class SwaggerRenderer < BaseRenderer
|
|
6
|
+
VERSION = "5.32.2"
|
|
7
|
+
|
|
8
|
+
def render
|
|
9
|
+
<<~HTML
|
|
10
|
+
<!DOCTYPE html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8">
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
15
|
+
<title>#{title}</title>
|
|
16
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@#{VERSION}/swagger-ui.css" />
|
|
17
|
+
<style>
|
|
18
|
+
html { box-sizing: border-box; overflow-y: scroll; }
|
|
19
|
+
*, *:before, *:after { box-sizing: inherit; }
|
|
20
|
+
body { margin: 0; background: #fafafa; }
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
#{nav_bar(active: :swagger)}
|
|
25
|
+
<div id="swagger-ui"></div>
|
|
26
|
+
<script src="https://unpkg.com/swagger-ui-dist@#{VERSION}/swagger-ui-bundle.js"></script>
|
|
27
|
+
<script>
|
|
28
|
+
SwaggerUIBundle({
|
|
29
|
+
url: #{spec_url_json},
|
|
30
|
+
dom_id: '#swagger-ui',
|
|
31
|
+
presets: [
|
|
32
|
+
SwaggerUIBundle.presets.apis,
|
|
33
|
+
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
34
|
+
],
|
|
35
|
+
layout: "BaseLayout",
|
|
36
|
+
deepLinking: true,
|
|
37
|
+
showExtensions: true,
|
|
38
|
+
showCommonExtensions: true,
|
|
39
|
+
requestInterceptor: function(req) {
|
|
40
|
+
var url = new URL(req.url);
|
|
41
|
+
url.protocol = window.location.protocol;
|
|
42
|
+
url.host = window.location.host;
|
|
43
|
+
req.url = url.toString();
|
|
44
|
+
return req;
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
</script>
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
50
|
+
HTML
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/docit/version.rb
CHANGED
data/lib/docit.rb
CHANGED
|
@@ -12,6 +12,9 @@ require_relative "docit/doc_file"
|
|
|
12
12
|
require_relative "docit/route_inspector"
|
|
13
13
|
require_relative "docit/schema_generator"
|
|
14
14
|
require_relative "docit/dsl"
|
|
15
|
+
require_relative "docit/ui/base_renderer"
|
|
16
|
+
require_relative "docit/ui/swagger_renderer"
|
|
17
|
+
require_relative "docit/ui/scalar_renderer"
|
|
15
18
|
|
|
16
19
|
# Docit is a decorator-style API documentation gem for Ruby on Rails.
|
|
17
20
|
# It generates OpenAPI 3.0.3 specs from clean DSL macros on your controllers.
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
Docit.configure do |config|
|
|
4
|
-
# The title shown in
|
|
4
|
+
# The title shown in the API documentation UI
|
|
5
5
|
config.title = "<%= Rails.application.class.module_parent_name rescue 'My API' %>"
|
|
6
6
|
|
|
7
7
|
# API version
|
|
8
8
|
config.version = "1.0.0"
|
|
9
9
|
|
|
10
|
-
# Description shown
|
|
10
|
+
# Description shown on the introduction page
|
|
11
11
|
config.description = "API documentation powered by Docit"
|
|
12
12
|
|
|
13
|
+
# Documentation UI: :scalar (default) or :swagger
|
|
14
|
+
# config.default_ui = :scalar
|
|
15
|
+
|
|
13
16
|
# Authentication scheme (options: :bearer, :basic, :api_key)
|
|
14
17
|
# config.auth :bearer
|
|
15
18
|
|
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.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- S13G
|
|
@@ -43,12 +43,15 @@ files:
|
|
|
43
43
|
- Rakefile
|
|
44
44
|
- app/controllers/docit/ui_controller.rb
|
|
45
45
|
- config/routes.rb
|
|
46
|
+
- docs/images/scalar_image.png
|
|
47
|
+
- docs/images/swagger_image.png
|
|
46
48
|
- lib/docit.rb
|
|
47
49
|
- lib/docit/ai.rb
|
|
48
50
|
- lib/docit/ai/anthropic_client.rb
|
|
49
51
|
- lib/docit/ai/autodoc_runner.rb
|
|
50
52
|
- lib/docit/ai/client.rb
|
|
51
53
|
- lib/docit/ai/configuration.rb
|
|
54
|
+
- lib/docit/ai/doc_block_validator.rb
|
|
52
55
|
- lib/docit/ai/doc_writer.rb
|
|
53
56
|
- lib/docit/ai/gap_detector.rb
|
|
54
57
|
- lib/docit/ai/groq_client.rb
|
|
@@ -68,6 +71,9 @@ files:
|
|
|
68
71
|
- lib/docit/route_inspector.rb
|
|
69
72
|
- lib/docit/schema_definition.rb
|
|
70
73
|
- lib/docit/schema_generator.rb
|
|
74
|
+
- lib/docit/ui/base_renderer.rb
|
|
75
|
+
- lib/docit/ui/scalar_renderer.rb
|
|
76
|
+
- lib/docit/ui/swagger_renderer.rb
|
|
71
77
|
- lib/docit/version.rb
|
|
72
78
|
- lib/generators/docit/ai_setup/ai_setup_generator.rb
|
|
73
79
|
- lib/generators/docit/install/install_generator.rb
|
|
@@ -80,7 +86,7 @@ licenses:
|
|
|
80
86
|
metadata:
|
|
81
87
|
homepage_uri: https://github.com/S13G/docit
|
|
82
88
|
source_code_uri: https://github.com/S13G/docit
|
|
83
|
-
changelog_uri: https://github.com/S13G/docit/blob/
|
|
89
|
+
changelog_uri: https://github.com/S13G/docit/blob/master/CHANGELOG.md
|
|
84
90
|
documentation_uri: https://rubydoc.info/gems/docit
|
|
85
91
|
bug_tracker_uri: https://github.com/S13G/docit/issues
|
|
86
92
|
rubygems_mfa_required: 'true'
|