trifle-docs 0.7.0 → 0.7.2
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/Gemfile.lock +1 -1
- data/README.md +18 -0
- data/lib/trifle/docs/app.rb +120 -62
- data/lib/trifle/docs/configuration.rb +2 -1
- data/lib/trifle/docs/engine.rb +45 -9
- data/lib/trifle/docs/helper/llms.rb +38 -14
- data/lib/trifle/docs/helper/markdown_layout.rb +1 -4
- data/lib/trifle/docs/helper/sitemap.rb +47 -7
- data/lib/trifle/docs/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4660d54f61927575620d127f9493bc2921bf4a0395362acd2f71b4eeaee38712
|
|
4
|
+
data.tar.gz: c59600b536c976dbf67eaf4f99a61d214b1022b6a18795cc7ea37d9f5caf4ba7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc4c3fa9f2d46e94c6a7a19169830b33c2601fbc2a39360945d57691332b3d3a6d00be8fdc12aa13a379595aefd655f887c02dcbd3ceda561c6431fdda1b0d2d
|
|
7
|
+
data.tar.gz: cd867ffc069b37ee0e513893abe097c8ed4c6a5ecdd8e7fb1d47c0fd0c33f950892024ca04ac653f3281383aab64cb3d7c88671ee5b5b8579b543b49112b3745
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -41,6 +41,8 @@ require 'trifle/docs'
|
|
|
41
41
|
Trifle::Docs.configure do |config|
|
|
42
42
|
config.path = File.join(__dir__, 'docs')
|
|
43
43
|
config.views = File.join(__dir__, 'templates')
|
|
44
|
+
# Optional: canonical base URL used for absolute <loc> values in sitemap.xml
|
|
45
|
+
# config.sitemap_base_url = ENV.fetch('TRIFLE_DOCS_SITEMAP_BASE_URL', nil)
|
|
44
46
|
config.register_harvester(Trifle::Docs::Harvester::Markdown)
|
|
45
47
|
config.register_harvester(Trifle::Docs::Harvester::File)
|
|
46
48
|
end
|
|
@@ -110,6 +112,22 @@ There are several variables available in your template file (except `layout.erb`
|
|
|
110
112
|
- **Caching support** - Optional caching for production environments
|
|
111
113
|
- **Navigation helpers** - Automatic menu and breadcrumb generation
|
|
112
114
|
|
|
115
|
+
## Sitemap URL configuration
|
|
116
|
+
|
|
117
|
+
`/sitemap.xml` emits absolute URLs. By default, Trifle::Docs uses the incoming request host (`request.base_url`).
|
|
118
|
+
|
|
119
|
+
If your app runs behind a proxy/ingress and you need a canonical public domain, set:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
config.sitemap_base_url = 'https://docs.trifle.io'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
In containerized deployments you can wire this through an env var, for example:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
config.sitemap_base_url = ENV.fetch('TRIFLE_DOCS_SITEMAP_BASE_URL', nil)
|
|
129
|
+
```
|
|
130
|
+
|
|
113
131
|
## Harvesters
|
|
114
132
|
|
|
115
133
|
Trifle::Docs supports multiple content processors:
|
data/lib/trifle/docs/app.rb
CHANGED
|
@@ -4,65 +4,11 @@ require 'sinatra/base'
|
|
|
4
4
|
|
|
5
5
|
module Trifle
|
|
6
6
|
module Docs
|
|
7
|
-
|
|
8
|
-
configure do
|
|
9
|
-
set :bind, '0.0.0.0'
|
|
10
|
-
set :views, proc { Trifle::Docs.default.views }
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
get '/search' do
|
|
14
|
-
results = Trifle::Docs.search(query: params['query'], scope: params['scope'])
|
|
15
|
-
erb(
|
|
16
|
-
'search'.to_sym,
|
|
17
|
-
{},
|
|
18
|
-
{
|
|
19
|
-
results: results,
|
|
20
|
-
query: params['query'],
|
|
21
|
-
scope: params['scope'],
|
|
22
|
-
sitemap: Trifle::Docs.sitemap,
|
|
23
|
-
meta: { description: 'Search' }
|
|
24
|
-
}
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
get '/llms.txt' do
|
|
29
|
-
meta = Trifle::Docs.meta(url: 'llms.txt')
|
|
30
|
-
return send_file(meta['path']) if meta && meta['type'] == 'file'
|
|
31
|
-
|
|
32
|
-
content = Trifle::Docs::Helper::Llms.homepage_markdown
|
|
33
|
-
halt(404, 'Not Found') if content.nil?
|
|
34
|
-
|
|
35
|
-
content_type 'text/markdown'
|
|
36
|
-
content
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
get '/llms-full.txt' do
|
|
40
|
-
meta = Trifle::Docs.meta(url: 'llms-full.txt')
|
|
41
|
-
return send_file(meta['path']) if meta && meta['type'] == 'file'
|
|
42
|
-
|
|
43
|
-
content = Trifle::Docs::Helper::Llms.full_markdown
|
|
44
|
-
halt(404, 'Not Found') if content.nil? || content.strip.empty?
|
|
45
|
-
|
|
46
|
-
content_type 'text/markdown'
|
|
47
|
-
content
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
get '/sitemap.xml' do
|
|
51
|
-
meta = Trifle::Docs.meta(url: 'sitemap.xml')
|
|
52
|
-
return send_file(meta['path']) if meta && meta['type'] == 'file'
|
|
53
|
-
|
|
54
|
-
render_generated_sitemap
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
get '/*' do
|
|
58
|
-
handle_request(params, request)
|
|
59
|
-
end
|
|
60
|
-
|
|
7
|
+
module AppHelpers
|
|
61
8
|
def markdown_requested?(request, params)
|
|
62
9
|
return true if params['format'].to_s.downcase == 'md'
|
|
63
10
|
|
|
64
|
-
|
|
65
|
-
accept.include?('text/markdown')
|
|
11
|
+
request.env['HTTP_ACCEPT'].to_s.include?('text/markdown')
|
|
66
12
|
end
|
|
67
13
|
|
|
68
14
|
def render_markdown?(meta, request, params)
|
|
@@ -77,18 +23,18 @@ module Trifle
|
|
|
77
23
|
meta = Trifle::Docs.meta(url: url)
|
|
78
24
|
halt(404, 'Not Found') if meta.nil?
|
|
79
25
|
|
|
26
|
+
set_vary_header unless file_meta?(meta)
|
|
80
27
|
return render_markdown(meta, url) if render_markdown?(meta, request, params)
|
|
81
|
-
return send_file(meta['path']) if meta
|
|
28
|
+
return send_file(meta['path']) if file_meta?(meta)
|
|
82
29
|
|
|
83
30
|
render_html(meta, url)
|
|
84
31
|
end
|
|
85
32
|
|
|
86
33
|
def render_markdown(meta, url)
|
|
87
|
-
content_type
|
|
34
|
+
content_type markdown_content_type(request)
|
|
88
35
|
Trifle::Docs::Helper::MarkdownLayout.render(
|
|
89
36
|
meta: meta,
|
|
90
|
-
raw_content: Trifle::Docs.raw_content(url: url)
|
|
91
|
-
sitemap: Trifle::Docs.sitemap
|
|
37
|
+
raw_content: Trifle::Docs.raw_content(url: url)
|
|
92
38
|
)
|
|
93
39
|
end
|
|
94
40
|
|
|
@@ -103,14 +49,126 @@ module Trifle
|
|
|
103
49
|
end
|
|
104
50
|
|
|
105
51
|
def render_generated_sitemap
|
|
106
|
-
|
|
52
|
+
base_url = Trifle::Docs.default.sitemap_base_url || request.base_url
|
|
53
|
+
content = Trifle::Docs::Helper::Sitemap.xml(base_url: base_url)
|
|
107
54
|
halt(404, 'Not Found') if content.nil? || content.strip.empty?
|
|
108
55
|
|
|
109
56
|
content_type 'application/xml'
|
|
110
57
|
content
|
|
111
58
|
end
|
|
112
59
|
|
|
113
|
-
|
|
60
|
+
def markdown_content_type(request)
|
|
61
|
+
return 'text/plain' if Trifle::Docs::Helper::AiDetection.ai_scraper?(request.user_agent)
|
|
62
|
+
|
|
63
|
+
'text/markdown'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def set_vary_header
|
|
67
|
+
headers['Vary'] = append_vary(headers['Vary'], 'User-Agent')
|
|
68
|
+
headers['Vary'] = append_vary(headers['Vary'], 'Accept')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def render_llms_for(base_url, full:)
|
|
72
|
+
base_url = normalize_base_url(base_url)
|
|
73
|
+
url = llms_url(base_url, full: full)
|
|
74
|
+
meta = Trifle::Docs.meta(url: url)
|
|
75
|
+
return send_file(meta['path']) if file_meta?(meta)
|
|
76
|
+
|
|
77
|
+
content = llms_content(full: full, base_url: base_url)
|
|
78
|
+
halt(404, 'Not Found') if llms_missing?(content, full: full)
|
|
79
|
+
|
|
80
|
+
content_type 'text/plain'
|
|
81
|
+
content
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def append_vary(existing, value)
|
|
87
|
+
values = existing.to_s.split(',').map(&:strip).reject(&:empty?)
|
|
88
|
+
return value if values.empty?
|
|
89
|
+
return existing if values.any? { |entry| entry.casecmp(value).zero? }
|
|
90
|
+
|
|
91
|
+
(values + [value]).join(', ')
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def normalize_base_url(base_url)
|
|
95
|
+
base_url.to_s.gsub(%r{^/+}, '').gsub(%r{/+$}, '')
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def llms_url(base_url, full:)
|
|
99
|
+
llms_name = full ? 'llms-full.txt' : 'llms.txt'
|
|
100
|
+
[base_url, llms_name].reject(&:empty?).join('/')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def llms_content(full:, base_url:)
|
|
104
|
+
if full
|
|
105
|
+
Trifle::Docs::Helper::Llms.full_markdown(base_url: base_url)
|
|
106
|
+
else
|
|
107
|
+
Trifle::Docs::Helper::Llms.homepage_markdown(base_url: base_url)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def llms_missing?(content, full:)
|
|
112
|
+
return true if content.nil?
|
|
113
|
+
return false unless full
|
|
114
|
+
|
|
115
|
+
content.strip.empty?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def file_meta?(meta)
|
|
119
|
+
meta && meta['type'] == 'file'
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class App < Sinatra::Base
|
|
124
|
+
configure do
|
|
125
|
+
set :bind, '0.0.0.0'
|
|
126
|
+
set :views, proc { Trifle::Docs.default.views }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
helpers AppHelpers
|
|
130
|
+
|
|
131
|
+
get '/search' do
|
|
132
|
+
results = Trifle::Docs.search(query: params['query'], scope: params['scope'])
|
|
133
|
+
erb(
|
|
134
|
+
'search'.to_sym,
|
|
135
|
+
{},
|
|
136
|
+
{
|
|
137
|
+
results: results,
|
|
138
|
+
query: params['query'],
|
|
139
|
+
scope: params['scope'],
|
|
140
|
+
sitemap: Trifle::Docs.sitemap,
|
|
141
|
+
meta: { description: 'Search' }
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
get '/llms.txt' do
|
|
147
|
+
render_llms_for('', full: false)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
get %r{/(.+)/llms\.txt} do |path|
|
|
151
|
+
render_llms_for(path, full: false)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
get '/llms-full.txt' do
|
|
155
|
+
render_llms_for('', full: true)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
get %r{/(.+)/llms-full\.txt} do |path|
|
|
159
|
+
render_llms_for(path, full: true)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
get '/sitemap.xml' do
|
|
163
|
+
meta = Trifle::Docs.meta(url: 'sitemap.xml')
|
|
164
|
+
return send_file(meta['path']) if meta && meta['type'] == 'file'
|
|
165
|
+
|
|
166
|
+
render_generated_sitemap
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
get '/*' do
|
|
170
|
+
handle_request(params, request)
|
|
171
|
+
end
|
|
114
172
|
end
|
|
115
173
|
end
|
|
116
174
|
end
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
module Trifle
|
|
4
4
|
module Docs
|
|
5
5
|
class Configuration
|
|
6
|
-
attr_accessor :path, :views, :layout, :namespace, :cache
|
|
6
|
+
attr_accessor :path, :views, :layout, :namespace, :cache, :sitemap_base_url
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
9
|
@harvesters = []
|
|
10
10
|
@path = nil
|
|
11
11
|
@namespace = nil
|
|
12
12
|
@cache = true
|
|
13
|
+
@sitemap_base_url = nil
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def harvester
|
data/lib/trifle/docs/engine.rb
CHANGED
|
@@ -19,6 +19,8 @@ if Object.const_defined?('Rails')
|
|
|
19
19
|
root to: 'page#show'
|
|
20
20
|
get 'llms.txt', to: 'page#llms'
|
|
21
21
|
get 'llms-full.txt', to: 'page#llms_full'
|
|
22
|
+
get '*path/llms.txt', to: 'page#llms'
|
|
23
|
+
get '*path/llms-full.txt', to: 'page#llms_full'
|
|
22
24
|
get 'sitemap.xml', to: 'page#sitemap'
|
|
23
25
|
get 'search', to: 'page#search'
|
|
24
26
|
get '*url', to: 'page#show'
|
|
@@ -37,7 +39,7 @@ if Object.const_defined?('Rails')
|
|
|
37
39
|
return render_not_found if content.nil?
|
|
38
40
|
return render_not_found if !allow_empty && content.strip.empty?
|
|
39
41
|
|
|
40
|
-
render plain: content, content_type: 'text/
|
|
42
|
+
render plain: content, content_type: 'text/plain'
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
def render_sitemap(url)
|
|
@@ -71,9 +73,8 @@ if Object.const_defined?('Rails')
|
|
|
71
73
|
def render_markdown(url:, meta:)
|
|
72
74
|
render plain: Trifle::Docs::Helper::MarkdownLayout.render(
|
|
73
75
|
meta: meta,
|
|
74
|
-
raw_content: Trifle::Docs.raw_content(url: url, config: configuration)
|
|
75
|
-
|
|
76
|
-
), content_type: 'text/markdown'
|
|
76
|
+
raw_content: Trifle::Docs.raw_content(url: url, config: configuration)
|
|
77
|
+
), content_type: markdown_content_type
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
def fetch_meta(url)
|
|
@@ -81,6 +82,7 @@ if Object.const_defined?('Rails')
|
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
def render_for_meta(meta, url, wants_md, request)
|
|
85
|
+
set_vary_header unless file_meta?(meta)
|
|
84
86
|
return render_markdown(url: url, meta: meta) if render_markdown?(meta, wants_md, request)
|
|
85
87
|
return render_file(meta: meta) if file_meta?(meta)
|
|
86
88
|
|
|
@@ -99,6 +101,10 @@ if Object.const_defined?('Rails')
|
|
|
99
101
|
[url, wants_md]
|
|
100
102
|
end
|
|
101
103
|
|
|
104
|
+
def llms_scope
|
|
105
|
+
params[:path].to_s.gsub(%r{^/+}, '').gsub(%r{/+$}, '')
|
|
106
|
+
end
|
|
107
|
+
|
|
102
108
|
def render_markdown?(meta, wants_md, request)
|
|
103
109
|
return false if meta['type'] == 'file'
|
|
104
110
|
|
|
@@ -110,6 +116,25 @@ if Object.const_defined?('Rails')
|
|
|
110
116
|
|
|
111
117
|
request.headers['Accept'].to_s.include?('text/markdown')
|
|
112
118
|
end
|
|
119
|
+
|
|
120
|
+
def markdown_content_type
|
|
121
|
+
return 'text/plain' if Trifle::Docs::Helper::AiDetection.ai_scraper?(request.user_agent)
|
|
122
|
+
|
|
123
|
+
'text/markdown'
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def set_vary_header
|
|
127
|
+
response.headers['Vary'] = append_vary(response.headers['Vary'], 'User-Agent')
|
|
128
|
+
response.headers['Vary'] = append_vary(response.headers['Vary'], 'Accept')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def append_vary(existing, value)
|
|
132
|
+
values = existing.to_s.split(',').map(&:strip).reject(&:empty?)
|
|
133
|
+
return value if values.empty?
|
|
134
|
+
return existing if values.any? { |entry| entry.casecmp(value).zero? }
|
|
135
|
+
|
|
136
|
+
(values + [value]).join(', ')
|
|
137
|
+
end
|
|
113
138
|
end
|
|
114
139
|
|
|
115
140
|
class PageController < ActionController::Base
|
|
@@ -146,20 +171,31 @@ if Object.const_defined?('Rails')
|
|
|
146
171
|
end
|
|
147
172
|
|
|
148
173
|
def llms
|
|
149
|
-
|
|
150
|
-
|
|
174
|
+
base_url = llms_scope
|
|
175
|
+
llms_url = [base_url, 'llms.txt'].reject(&:empty?).join('/')
|
|
176
|
+
render_llms(llms_url, allow_empty: true) do
|
|
177
|
+
Trifle::Docs::Helper::Llms.homepage_markdown(
|
|
178
|
+
config: configuration,
|
|
179
|
+
base_url: base_url
|
|
180
|
+
)
|
|
151
181
|
end
|
|
152
182
|
end
|
|
153
183
|
|
|
154
184
|
def llms_full
|
|
155
|
-
|
|
156
|
-
|
|
185
|
+
base_url = llms_scope
|
|
186
|
+
llms_url = [base_url, 'llms-full.txt'].reject(&:empty?).join('/')
|
|
187
|
+
render_llms(llms_url) do
|
|
188
|
+
Trifle::Docs::Helper::Llms.full_markdown(
|
|
189
|
+
config: configuration,
|
|
190
|
+
base_url: base_url
|
|
191
|
+
)
|
|
157
192
|
end
|
|
158
193
|
end
|
|
159
194
|
|
|
160
195
|
def sitemap
|
|
196
|
+
base_url = configuration.sitemap_base_url || request.base_url
|
|
161
197
|
render_sitemap('sitemap.xml') do
|
|
162
|
-
Trifle::Docs::Helper::Sitemap.xml(config: configuration)
|
|
198
|
+
Trifle::Docs::Helper::Sitemap.xml(config: configuration, base_url: base_url)
|
|
163
199
|
end
|
|
164
200
|
end
|
|
165
201
|
end
|
|
@@ -6,32 +6,56 @@ module Trifle
|
|
|
6
6
|
module Llms
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
|
-
def homepage_markdown(config: nil)
|
|
10
|
-
|
|
9
|
+
def homepage_markdown(config: nil, base_url: '')
|
|
10
|
+
base_url = normalize_base_url(base_url)
|
|
11
|
+
meta = Trifle::Docs.meta(url: base_url, config: config)
|
|
11
12
|
return nil if meta.nil?
|
|
12
13
|
|
|
13
14
|
Trifle::Docs::Helper::MarkdownLayout.render(
|
|
14
15
|
meta: meta,
|
|
15
|
-
raw_content: Trifle::Docs.raw_content(url:
|
|
16
|
-
sitemap: Trifle::Docs.sitemap(config: config)
|
|
16
|
+
raw_content: Trifle::Docs.raw_content(url: base_url, config: config)
|
|
17
17
|
)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def full_markdown(config: nil)
|
|
20
|
+
def full_markdown(config: nil, base_url: '')
|
|
21
|
+
pages = llms_pages(config: config, base_url: base_url)
|
|
22
|
+
return nil if pages.nil?
|
|
23
|
+
|
|
24
|
+
pages.filter_map { |page| render_llms_page(page, config: config) }
|
|
25
|
+
.join("\n\n")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def llms_pages(config:, base_url:)
|
|
29
|
+
base_url = normalize_base_url(base_url)
|
|
21
30
|
sitemap = Trifle::Docs.sitemap(config: config)
|
|
22
|
-
|
|
31
|
+
scoped_sitemap = sitemap_subtree(sitemap, base_url)
|
|
32
|
+
return nil if scoped_sitemap.nil?
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
path = base_url.empty? ? [] : base_url.split('/')
|
|
35
|
+
flatten_sitemap(scoped_sitemap, path)
|
|
36
|
+
end
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
def render_llms_page(page, config:)
|
|
39
|
+
meta = page[:meta]
|
|
40
|
+
return nil if meta.nil? || meta['type'] == 'file'
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
raw = Trifle::Docs.raw_content(url: page[:url], config: config)
|
|
43
|
+
return nil if raw.nil? || raw.strip.empty?
|
|
44
|
+
|
|
45
|
+
format_page(meta: meta, url: page[:url], raw_content: raw)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def sitemap_subtree(sitemap, base_url)
|
|
49
|
+
return nil unless sitemap.is_a?(Hash)
|
|
50
|
+
|
|
51
|
+
base_url = normalize_base_url(base_url)
|
|
52
|
+
return sitemap if base_url.empty?
|
|
53
|
+
|
|
54
|
+
sitemap.dig(*base_url.split('/'))
|
|
55
|
+
end
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
def normalize_base_url(base_url)
|
|
58
|
+
base_url.to_s.gsub(%r{^/+}, '').gsub(%r{/+$}, '')
|
|
35
59
|
end
|
|
36
60
|
|
|
37
61
|
def flatten_sitemap(node, path = [])
|
|
@@ -6,15 +6,12 @@ module Trifle
|
|
|
6
6
|
module MarkdownLayout
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
|
-
def render(meta:, raw_content
|
|
9
|
+
def render(meta:, raw_content:)
|
|
10
10
|
lines = []
|
|
11
11
|
title = meta['title'] || derive_title_from_url(meta['url'])
|
|
12
12
|
|
|
13
13
|
lines << "# #{title}"
|
|
14
14
|
lines << ''
|
|
15
|
-
lines << '## Navigation'
|
|
16
|
-
lines << navigation_toc(sitemap)
|
|
17
|
-
lines << ''
|
|
18
15
|
lines << '## Content'
|
|
19
16
|
lines << raw_content.to_s.strip
|
|
20
17
|
lines << ''
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'cgi'
|
|
4
4
|
require 'time'
|
|
5
|
+
require 'uri'
|
|
5
6
|
|
|
6
7
|
module Trifle
|
|
7
8
|
module Docs
|
|
@@ -9,9 +10,10 @@ module Trifle
|
|
|
9
10
|
module Sitemap
|
|
10
11
|
module_function
|
|
11
12
|
|
|
12
|
-
def xml(config: nil)
|
|
13
|
+
def xml(config: nil, base_url: nil)
|
|
13
14
|
sitemap = Trifle::Docs.sitemap(config: config)
|
|
14
|
-
|
|
15
|
+
resolved_base_url = resolve_base_url(config: config, base_url: base_url)
|
|
16
|
+
urls = sitemap_urls(sitemap, base_url: resolved_base_url)
|
|
15
17
|
return nil if urls.empty?
|
|
16
18
|
|
|
17
19
|
build_document(urls)
|
|
@@ -31,13 +33,13 @@ module Trifle
|
|
|
31
33
|
entries
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
def sitemap_urls(sitemap)
|
|
36
|
+
def sitemap_urls(sitemap, base_url: nil)
|
|
35
37
|
entries = flatten_sitemap(sitemap)
|
|
36
38
|
entries.filter_map do |entry|
|
|
37
39
|
meta = entry[:meta]
|
|
38
40
|
next if meta.nil? || meta['type'] == 'file'
|
|
39
41
|
|
|
40
|
-
build_url_entry(entry[:url], meta)
|
|
42
|
+
build_url_entry(entry[:url], meta, base_url: base_url)
|
|
41
43
|
end
|
|
42
44
|
end
|
|
43
45
|
|
|
@@ -50,21 +52,59 @@ module Trifle
|
|
|
50
52
|
].join("\n")
|
|
51
53
|
end
|
|
52
54
|
|
|
53
|
-
def build_url_entry(url, meta)
|
|
54
|
-
loc = normalize_loc(url, meta)
|
|
55
|
+
def build_url_entry(url, meta, base_url: nil)
|
|
56
|
+
loc = normalize_loc(url, meta, base_url: base_url)
|
|
55
57
|
lastmod = format_lastmod(meta['updated_at'])
|
|
56
58
|
lastmod_tag = lastmod ? "<lastmod>#{lastmod}</lastmod>" : ''
|
|
57
59
|
|
|
58
60
|
"<url><loc>#{loc}</loc>#{lastmod_tag}</url>"
|
|
59
61
|
end
|
|
60
62
|
|
|
61
|
-
def normalize_loc(url, meta)
|
|
63
|
+
def normalize_loc(url, meta, base_url: nil)
|
|
62
64
|
loc = meta['url']
|
|
63
65
|
loc = "/#{url}" if loc.nil? || loc.empty?
|
|
64
66
|
loc = '/' if loc == '//'
|
|
67
|
+
loc = absolutize_loc(loc, base_url)
|
|
65
68
|
CGI.escapeHTML(loc)
|
|
66
69
|
end
|
|
67
70
|
|
|
71
|
+
def absolutize_loc(loc, base_url)
|
|
72
|
+
return loc if base_url.nil? || absolute_url?(loc)
|
|
73
|
+
|
|
74
|
+
path = loc.to_s.gsub(%r{^/+}, '')
|
|
75
|
+
return "#{base_url}/" if path.empty?
|
|
76
|
+
|
|
77
|
+
"#{base_url}/#{path}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def absolute_url?(loc)
|
|
81
|
+
loc.to_s.match?(%r{\Ahttps?://}i)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def resolve_base_url(config:, base_url:)
|
|
85
|
+
normalize_base_url(base_url) || normalize_base_url(configuration_base_url(config))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def configuration_base_url(config)
|
|
89
|
+
return nil unless config.respond_to?(:sitemap_base_url)
|
|
90
|
+
|
|
91
|
+
config.sitemap_base_url
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def normalize_base_url(base_url)
|
|
95
|
+
value = base_url.to_s.strip
|
|
96
|
+
return nil if value.empty?
|
|
97
|
+
|
|
98
|
+
uri = URI.parse(value)
|
|
99
|
+
return nil unless uri.is_a?(URI::HTTP) && uri.host
|
|
100
|
+
|
|
101
|
+
uri.query = nil
|
|
102
|
+
uri.fragment = nil
|
|
103
|
+
uri.to_s.gsub(%r{/+$}, '')
|
|
104
|
+
rescue URI::InvalidURIError
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
68
108
|
def format_lastmod(value)
|
|
69
109
|
return nil if value.nil?
|
|
70
110
|
|
data/lib/trifle/docs/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trifle-docs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jozef Vaclavik
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-02-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|