govuk_tech_docs 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +90 -0
- data/docs/configuration.md +15 -0
- data/docs/frontmatter.md +2 -14
- data/docs/page-expiry.md +69 -0
- data/example/Gemfile +1 -0
- data/example/config/tech-docs.yml +6 -0
- data/example/source/api-path.html.md +7 -0
- data/example/source/api-reference.html.md +5 -0
- data/example/source/pets.yml +106 -0
- data/govuk_tech_docs.gemspec +2 -0
- data/lib/assets/javascripts/_analytics.js +12 -0
- data/lib/assets/javascripts/_modules/collapsible-navigation.js +5 -3
- data/lib/assets/javascripts/_modules/search.js +175 -6
- data/lib/assets/stylesheets/modules/_collapsible.scss +12 -5
- data/lib/assets/stylesheets/modules/_technical-documentation.scss +16 -11
- data/lib/assets/stylesheets/modules/_toc.scss +1 -1
- data/lib/govuk_tech_docs.rb +13 -2
- data/lib/govuk_tech_docs/api_reference/api_reference_extension.rb +100 -0
- data/lib/govuk_tech_docs/api_reference/api_reference_renderer.rb +279 -0
- data/lib/govuk_tech_docs/api_reference/templates/api_reference_full.html.erb +9 -0
- data/lib/govuk_tech_docs/api_reference/templates/operation.html.erb +11 -0
- data/lib/govuk_tech_docs/api_reference/templates/parameters.html.erb +28 -0
- data/lib/govuk_tech_docs/api_reference/templates/path.html.erb +4 -0
- data/lib/govuk_tech_docs/api_reference/templates/responses.html.erb +33 -0
- data/lib/govuk_tech_docs/api_reference/templates/schema.html.erb +29 -0
- data/lib/govuk_tech_docs/page_review.rb +15 -3
- data/lib/govuk_tech_docs/pages.rb +3 -2
- data/lib/govuk_tech_docs/tech_docs_html_renderer.rb +10 -0
- data/lib/govuk_tech_docs/version.rb +1 -1
- data/lib/source/layouts/_header.erb +2 -4
- metadata +42 -4
- data/lib/source/images/arrow-down.svg +0 -9
- data/lib/source/images/arrow-up.svg +0 -9
@@ -11,6 +11,10 @@
|
|
11
11
|
display: block
|
12
12
|
}
|
13
13
|
}
|
14
|
+
.collapsible__heading,
|
15
|
+
.toc__list > ul > li > a:link.collapsible__heading {
|
16
|
+
margin-right: 30px;
|
17
|
+
}
|
14
18
|
.collapsible__toggle {
|
15
19
|
position: absolute;
|
16
20
|
top: 0;
|
@@ -34,12 +38,15 @@
|
|
34
38
|
&::after {
|
35
39
|
content: '';
|
36
40
|
display: block;
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
+
border: 2px solid $black;
|
42
|
+
border-width: 2px 2px 0 0;
|
43
|
+
transform: rotate(135deg);
|
44
|
+
width: 10px;
|
45
|
+
height: 10px;
|
46
|
+
margin-top: 10px;
|
41
47
|
}
|
42
48
|
.collapsible.is-open &::after {
|
43
|
-
|
49
|
+
transform: rotate(315deg);
|
50
|
+
margin-top: 18px;
|
44
51
|
}
|
45
52
|
}
|
@@ -3,11 +3,11 @@
|
|
3
3
|
@mixin heading-offset($tabletTopMargin) {
|
4
4
|
// Scale margins with font size on mobile (16/19ths)
|
5
5
|
$mobileTopMargin: ceil($tabletTopMargin * (16 / 19));
|
6
|
-
|
6
|
+
|
7
7
|
// Offset headings down on mobile so that linking to anchors they appear after
|
8
8
|
// the sticky 'table of contents' element
|
9
9
|
$stickyTocOffset: 20px + $gutter-half + 10px + 1px;
|
10
|
-
|
10
|
+
|
11
11
|
// Pad the heading so that when linking to an anchor there is at most a
|
12
12
|
// $gutter-half (mobile) or $gutter (tablet and above) sized gap between the
|
13
13
|
// top of the viewport and the heading.
|
@@ -25,9 +25,9 @@
|
|
25
25
|
display: block;
|
26
26
|
margin: 0 $gutter-half 10px;
|
27
27
|
max-width: 40em;
|
28
|
-
|
28
|
+
|
29
29
|
line-height: 1.4;
|
30
|
-
|
30
|
+
|
31
31
|
color: $text-colour;
|
32
32
|
|
33
33
|
@include media(tablet) {
|
@@ -45,14 +45,14 @@
|
|
45
45
|
@include bold-48;
|
46
46
|
@include heading-offset($gutter * 2);
|
47
47
|
border-top: 5px solid $text-colour;
|
48
|
-
|
48
|
+
|
49
49
|
&:first-of-type {
|
50
50
|
@include heading-offset($gutter);
|
51
51
|
border-top: none;
|
52
52
|
}
|
53
53
|
}
|
54
54
|
|
55
|
-
h2 {
|
55
|
+
h2 {
|
56
56
|
@include bold-36;
|
57
57
|
@include heading-offset($gutter * 1.5);
|
58
58
|
}
|
@@ -125,7 +125,7 @@
|
|
125
125
|
|
126
126
|
ol + p, ul + p, .table-container + p {
|
127
127
|
margin-top: ceil($gutter * (16 / 19));
|
128
|
-
|
128
|
+
|
129
129
|
@include media(tablet) {
|
130
130
|
margin-top: $gutter;
|
131
131
|
}
|
@@ -144,6 +144,11 @@
|
|
144
144
|
overflow: auto;
|
145
145
|
position: relative;
|
146
146
|
border: 1px solid $code-02;
|
147
|
+
// Restrict the width of pre tags, as they have a tendency grow larger than
|
148
|
+
// the viewport when placed within table cells.
|
149
|
+
// @todo: Use table-layout: fixed, and remove the max-width definition from
|
150
|
+
// .technical-documentation so tables can fill the viewport.
|
151
|
+
max-width: 40em;
|
147
152
|
}
|
148
153
|
|
149
154
|
pre code {
|
@@ -156,11 +161,11 @@
|
|
156
161
|
background: $code-01;
|
157
162
|
padding: 3px 5px;
|
158
163
|
border-radius: 1px;
|
159
|
-
|
164
|
+
|
160
165
|
font-family: monaco, Consolas, "Lucida Console", monospace;
|
161
166
|
font-size: 15px;
|
162
167
|
color: $code-0E;
|
163
|
-
|
168
|
+
|
164
169
|
@include media(tablet) {
|
165
170
|
font-size: 16px;
|
166
171
|
}
|
@@ -191,11 +196,11 @@
|
|
191
196
|
display: block;
|
192
197
|
max-width: 100%;
|
193
198
|
overflow-x: auto;
|
194
|
-
|
199
|
+
|
195
200
|
margin-top: $gutter-half;
|
196
201
|
}
|
197
202
|
|
198
|
-
table {
|
203
|
+
table {
|
199
204
|
width: 100%;
|
200
205
|
|
201
206
|
border-collapse: collapse;
|
data/lib/govuk_tech_docs.rb
CHANGED
@@ -20,6 +20,7 @@ require 'govuk_tech_docs/pages'
|
|
20
20
|
require 'govuk_tech_docs/tech_docs_html_renderer'
|
21
21
|
require 'govuk_tech_docs/unique_identifier_extension'
|
22
22
|
require 'govuk_tech_docs/unique_identifier_generator'
|
23
|
+
require 'govuk_tech_docs/api_reference/api_reference_extension'
|
23
24
|
|
24
25
|
module GovukTechDocs
|
25
26
|
# Configure the tech docs template
|
@@ -37,7 +38,9 @@ module GovukTechDocs
|
|
37
38
|
context.set :markdown_engine, :redcarpet
|
38
39
|
context.set :markdown,
|
39
40
|
renderer: TechDocsHTMLRenderer.new(
|
40
|
-
with_toc_data: true
|
41
|
+
with_toc_data: true,
|
42
|
+
api: true,
|
43
|
+
context: context
|
41
44
|
),
|
42
45
|
fenced_code_blocks: true,
|
43
46
|
tables: true,
|
@@ -56,6 +59,8 @@ module GovukTechDocs
|
|
56
59
|
context.config[:tech_docs] = YAML.load_file('config/tech-docs.yml').with_indifferent_access
|
57
60
|
context.activate :unique_identifier
|
58
61
|
|
62
|
+
context.activate :api_reference
|
63
|
+
|
59
64
|
context.helpers do
|
60
65
|
include GovukTechDocs::TableOfContents::Helpers
|
61
66
|
include GovukTechDocs::ContributionBanner
|
@@ -65,7 +70,7 @@ module GovukTechDocs
|
|
65
70
|
end
|
66
71
|
|
67
72
|
def current_page_review
|
68
|
-
@current_page_review ||= GovukTechDocs::PageReview.new(current_page)
|
73
|
+
@current_page_review ||= GovukTechDocs::PageReview.new(current_page, config)
|
69
74
|
end
|
70
75
|
|
71
76
|
def format_date(date)
|
@@ -102,6 +107,12 @@ module GovukTechDocs
|
|
102
107
|
content: { boost: 50, store: true },
|
103
108
|
url: { index: false, store: true },
|
104
109
|
}
|
110
|
+
|
111
|
+
search.pipeline_remove = [
|
112
|
+
'stopWordFilter'
|
113
|
+
]
|
114
|
+
|
115
|
+
search.tokenizer_separator = '/[\s\-/]+/'
|
105
116
|
end
|
106
117
|
end
|
107
118
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'openapi3_parser'
|
3
|
+
require 'uri'
|
4
|
+
require 'pry'
|
5
|
+
require 'govuk_tech_docs/api_reference/api_reference_renderer'
|
6
|
+
|
7
|
+
module GovukTechDocs
|
8
|
+
module ApiReference
|
9
|
+
class Extension < Middleman::Extension
|
10
|
+
expose_to_application api: :api
|
11
|
+
|
12
|
+
def initialize(app, options_hash = {}, &block)
|
13
|
+
super
|
14
|
+
|
15
|
+
@app = app
|
16
|
+
@config = @app.config[:tech_docs]
|
17
|
+
|
18
|
+
# If no api path then just return.
|
19
|
+
if @config['api_path'].to_s.empty?
|
20
|
+
raise 'No api path defined in tech-docs.yml'
|
21
|
+
end
|
22
|
+
|
23
|
+
# Is the api_path a url or path?
|
24
|
+
if uri?(@config['api_path'])
|
25
|
+
@api_parser = true
|
26
|
+
@document = Openapi3Parser.load_url(@config['api_path'])
|
27
|
+
elsif File.exist?(@config['api_path'])
|
28
|
+
# Load api file and set existence flag.
|
29
|
+
@api_parser = true
|
30
|
+
@document = Openapi3Parser.load_file(@config['api_path'])
|
31
|
+
else
|
32
|
+
@api_parser = false
|
33
|
+
raise 'Unable to load api path from tech-docs.yml'
|
34
|
+
end
|
35
|
+
@render = Renderer.new(@app, @document)
|
36
|
+
end
|
37
|
+
|
38
|
+
def uri?(string)
|
39
|
+
uri = URI.parse(string)
|
40
|
+
%w(http https).include?(uri.scheme)
|
41
|
+
rescue URI::BadURIError
|
42
|
+
false
|
43
|
+
rescue URI::InvalidURIError
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def api(text)
|
48
|
+
if @api_parser == true
|
49
|
+
|
50
|
+
keywords = {
|
51
|
+
'api>' => 'default',
|
52
|
+
'api_schema>' => 'schema'
|
53
|
+
}
|
54
|
+
|
55
|
+
regexp = keywords.map { |k, _| Regexp.escape(k) }.join('|')
|
56
|
+
|
57
|
+
md = text.match(/^<p>(#{regexp})/)
|
58
|
+
if md
|
59
|
+
key = md.captures[0]
|
60
|
+
type = keywords[key]
|
61
|
+
|
62
|
+
text.gsub!(/#{ Regexp.escape(key) }\s+?/, '')
|
63
|
+
|
64
|
+
# Strip paragraph tags from text
|
65
|
+
text = text.gsub(/<\/?[^>]*>/, '')
|
66
|
+
text = text.strip
|
67
|
+
|
68
|
+
if text == 'api>'
|
69
|
+
@render.api_full(api_info, api_server)
|
70
|
+
elsif type == 'default'
|
71
|
+
output = @render.path(text)
|
72
|
+
# Render any schemas referenced in the above path
|
73
|
+
output += @render.schemas_from_path(text)
|
74
|
+
output
|
75
|
+
else
|
76
|
+
@render.schema(text)
|
77
|
+
end
|
78
|
+
|
79
|
+
else
|
80
|
+
return text
|
81
|
+
end
|
82
|
+
else
|
83
|
+
text
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def api_info
|
90
|
+
@document.info
|
91
|
+
end
|
92
|
+
|
93
|
+
def api_server
|
94
|
+
@document.servers[0]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
::Middleman::Extensions.register(:api_reference, GovukTechDocs::ApiReference::Extension)
|
@@ -0,0 +1,279 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module GovukTechDocs
|
5
|
+
module ApiReference
|
6
|
+
class Renderer
|
7
|
+
def initialize(app, document)
|
8
|
+
@app = app
|
9
|
+
@document = document
|
10
|
+
|
11
|
+
# Load template files
|
12
|
+
@template_api_full = get_renderer('api_reference_full.html.erb')
|
13
|
+
@template_path = get_renderer('path.html.erb')
|
14
|
+
@template_schema = get_renderer('schema.html.erb')
|
15
|
+
@template_operation = get_renderer('operation.html.erb')
|
16
|
+
@template_parameters = get_renderer('parameters.html.erb')
|
17
|
+
@template_responses = get_renderer('responses.html.erb')
|
18
|
+
end
|
19
|
+
|
20
|
+
def api_full(info, server)
|
21
|
+
paths = ''
|
22
|
+
paths_data = @document.paths
|
23
|
+
paths_data.each do |path_data|
|
24
|
+
# For some reason paths.each returns an array of arrays [title, object]
|
25
|
+
# instead of an array of objects
|
26
|
+
text = path_data[0]
|
27
|
+
paths += path(text)
|
28
|
+
end
|
29
|
+
schemas = ''
|
30
|
+
schemas_data = @document.components.schemas
|
31
|
+
schemas_data.each do |schema_data|
|
32
|
+
text = schema_data[0]
|
33
|
+
schemas += schema(text)
|
34
|
+
end
|
35
|
+
@template_api_full.result(binding)
|
36
|
+
end
|
37
|
+
|
38
|
+
def path(text)
|
39
|
+
path = @document.paths[text]
|
40
|
+
id = text.parameterize
|
41
|
+
operations = operations(path, id)
|
42
|
+
@template_path.result(binding)
|
43
|
+
end
|
44
|
+
|
45
|
+
def schema(text)
|
46
|
+
schemas = ''
|
47
|
+
schemas_data = @document.components.schemas
|
48
|
+
schemas_data.each do |schema_data|
|
49
|
+
all_of = schema_data[1]["allOf"]
|
50
|
+
properties = []
|
51
|
+
if !all_of.blank?
|
52
|
+
all_of.each do |schema_nested|
|
53
|
+
schema_nested.properties.each do |property|
|
54
|
+
properties.push property
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
schema_data[1].properties.each do |property|
|
60
|
+
properties.push property
|
61
|
+
end
|
62
|
+
|
63
|
+
if schema_data[0] == text
|
64
|
+
title = schema_data[0]
|
65
|
+
schema = schema_data[1]
|
66
|
+
return @template_schema.result(binding)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def schemas_from_path(text)
|
72
|
+
path = @document.paths[text]
|
73
|
+
operations = get_operations(path)
|
74
|
+
# Get all referenced schemas
|
75
|
+
schemas = []
|
76
|
+
operations.compact.each_value do |operation|
|
77
|
+
responses = operation.responses
|
78
|
+
responses.each do |_rkey, response|
|
79
|
+
if response.content['application/json']
|
80
|
+
schema = response.content['application/json'].schema
|
81
|
+
schema_name = get_schema_name(schema.node_context.source_location.to_s)
|
82
|
+
if !schema_name.nil?
|
83
|
+
schemas.push schema_name
|
84
|
+
end
|
85
|
+
schemas.concat(schemas_from_schema(schema))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
# Render all referenced schemas
|
90
|
+
output = ''
|
91
|
+
schemas.uniq.each do |schema_name|
|
92
|
+
output += schema(schema_name)
|
93
|
+
end
|
94
|
+
if !output.empty?
|
95
|
+
output.prepend('<h2 id="schemas">Schemas</h2>')
|
96
|
+
end
|
97
|
+
output
|
98
|
+
end
|
99
|
+
|
100
|
+
def schemas_from_schema(schema)
|
101
|
+
schemas = []
|
102
|
+
properties = []
|
103
|
+
schema.properties.each do |property|
|
104
|
+
properties.push property[1]
|
105
|
+
end
|
106
|
+
if schema.type == 'array'
|
107
|
+
properties.push schema.items
|
108
|
+
end
|
109
|
+
all_of = schema["allOf"]
|
110
|
+
if !all_of.blank?
|
111
|
+
all_of.each do |schema_nested|
|
112
|
+
schema_nested.properties.each do |property|
|
113
|
+
properties.push property[1]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
properties.each do |property|
|
118
|
+
# Must be a schema be referenced by another schema
|
119
|
+
# And not a property of a schema
|
120
|
+
if property.node_context.referenced_by.to_s.include?('#/components/schemas') &&
|
121
|
+
!property.node_context.source_location.to_s.include?('/properties/')
|
122
|
+
schema_name = get_schema_name(property.node_context.source_location.to_s)
|
123
|
+
end
|
124
|
+
if !schema_name.nil?
|
125
|
+
schemas.push schema_name
|
126
|
+
end
|
127
|
+
# Check sub-properties for references
|
128
|
+
schemas.concat(schemas_from_schema(property))
|
129
|
+
end
|
130
|
+
schemas
|
131
|
+
end
|
132
|
+
|
133
|
+
def operations(path, path_id)
|
134
|
+
output = ''
|
135
|
+
operations = get_operations(path)
|
136
|
+
operations.compact.each do |key, operation|
|
137
|
+
id = "#{path_id}-#{key.parameterize}"
|
138
|
+
parameters = parameters(operation, id)
|
139
|
+
responses = responses(operation, id)
|
140
|
+
output += @template_operation.result(binding)
|
141
|
+
end
|
142
|
+
output
|
143
|
+
end
|
144
|
+
|
145
|
+
def parameters(operation, operation_id)
|
146
|
+
parameters = operation.parameters
|
147
|
+
id = "#{operation_id}-parameters"
|
148
|
+
output = @template_parameters.result(binding)
|
149
|
+
output
|
150
|
+
end
|
151
|
+
|
152
|
+
def responses(operation, operation_id)
|
153
|
+
responses = operation.responses
|
154
|
+
id = "#{operation_id}-responses"
|
155
|
+
output = @template_responses.result(binding)
|
156
|
+
output
|
157
|
+
end
|
158
|
+
|
159
|
+
def markdown(text)
|
160
|
+
if text
|
161
|
+
Tilt['markdown'].new(context: @app) { text }.render
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def json_output(schema)
|
166
|
+
properties = schema_properties(schema)
|
167
|
+
JSON.pretty_generate(properties)
|
168
|
+
end
|
169
|
+
|
170
|
+
def json_prettyprint(data)
|
171
|
+
JSON.pretty_generate(data)
|
172
|
+
end
|
173
|
+
|
174
|
+
def schema_properties(schema_data)
|
175
|
+
properties = Hash.new
|
176
|
+
if defined? schema_data.properties
|
177
|
+
schema_data.properties.each do |key, property|
|
178
|
+
properties[key] = property
|
179
|
+
end
|
180
|
+
end
|
181
|
+
properties.merge! get_all_of_hash(schema_data)
|
182
|
+
properties_hash = Hash.new
|
183
|
+
properties.each do |pkey, property|
|
184
|
+
if property.type == 'object'
|
185
|
+
properties_hash[pkey] = Hash.new
|
186
|
+
items = property.items
|
187
|
+
if !items.blank?
|
188
|
+
properties_hash[pkey] = schema_properties(items)
|
189
|
+
end
|
190
|
+
if !property.properties.blank?
|
191
|
+
properties_hash[pkey] = schema_properties(property)
|
192
|
+
end
|
193
|
+
elsif property.type == 'array'
|
194
|
+
properties_hash[pkey] = Array.new
|
195
|
+
items = property.items
|
196
|
+
if !items.blank?
|
197
|
+
properties_hash[pkey].push schema_properties(items)
|
198
|
+
end
|
199
|
+
else
|
200
|
+
properties_hash[pkey] = !property.example.nil? ? property.example : property.type
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
properties_hash
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def get_all_of_array(schema)
|
210
|
+
properties = Array.new
|
211
|
+
if schema.is_a?(Array)
|
212
|
+
schema = schema[1]
|
213
|
+
end
|
214
|
+
if schema["allOf"]
|
215
|
+
all_of = schema["allOf"]
|
216
|
+
end
|
217
|
+
if !all_of.blank?
|
218
|
+
all_of.each do |schema_nested|
|
219
|
+
schema_nested.properties.each do |property|
|
220
|
+
if property.is_a?(Array)
|
221
|
+
property = property[1]
|
222
|
+
end
|
223
|
+
properties.push property
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
properties
|
228
|
+
end
|
229
|
+
|
230
|
+
def get_all_of_hash(schema)
|
231
|
+
properties = Hash.new
|
232
|
+
if schema["allOf"]
|
233
|
+
all_of = schema["allOf"]
|
234
|
+
end
|
235
|
+
if !all_of.blank?
|
236
|
+
all_of.each do |schema_nested|
|
237
|
+
schema_nested.properties.each do |key, property|
|
238
|
+
properties[key] = property
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
properties
|
243
|
+
end
|
244
|
+
|
245
|
+
def get_renderer(file)
|
246
|
+
template_path = File.join(File.dirname(__FILE__), 'templates/' + file)
|
247
|
+
template = File.open(template_path, 'r').read
|
248
|
+
ERB.new(template)
|
249
|
+
end
|
250
|
+
|
251
|
+
def get_operations(path)
|
252
|
+
operations = {}
|
253
|
+
operations['get'] = path.get if defined? path.get
|
254
|
+
operations['put'] = path.put if defined? path.put
|
255
|
+
operations['post'] = path.post if defined? path.post
|
256
|
+
operations['delete'] = path.delete if defined? path.delete
|
257
|
+
operations['patch'] = path.patch if defined? path.patch
|
258
|
+
operations
|
259
|
+
end
|
260
|
+
|
261
|
+
def get_schema_name(text)
|
262
|
+
unless text.is_a?(String)
|
263
|
+
return nil
|
264
|
+
end
|
265
|
+
# Schema dictates that it's always components['schemas']
|
266
|
+
text.gsub(/#\/components\/schemas\//, '')
|
267
|
+
end
|
268
|
+
|
269
|
+
def get_schema_link(schema)
|
270
|
+
schema_name = get_schema_name schema.node_context.source_location.to_s
|
271
|
+
if !schema_name.nil?
|
272
|
+
id = "schema-#{schema_name.parameterize}"
|
273
|
+
output = "<a href='\##{id}'>#{schema_name}</a>"
|
274
|
+
output
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|