docyard 0.1.0 → 0.2.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +23 -1
  4. data/README.md +16 -8
  5. data/lib/docyard/constants.rb +23 -0
  6. data/lib/docyard/errors.rb +54 -0
  7. data/lib/docyard/file_watcher.rb +2 -2
  8. data/lib/docyard/initializer.rb +9 -2
  9. data/lib/docyard/logging.rb +43 -0
  10. data/lib/docyard/rack_application.rb +29 -11
  11. data/lib/docyard/renderer.rb +5 -3
  12. data/lib/docyard/router.rb +13 -8
  13. data/lib/docyard/routing/resolution_result.rb +31 -0
  14. data/lib/docyard/sidebar/file_system_scanner.rb +77 -0
  15. data/lib/docyard/sidebar/renderer.rb +110 -0
  16. data/lib/docyard/sidebar/title_extractor.rb +25 -0
  17. data/lib/docyard/sidebar/tree_builder.rb +59 -0
  18. data/lib/docyard/sidebar_builder.rb +50 -0
  19. data/lib/docyard/templates/assets/css/code.css +214 -0
  20. data/lib/docyard/templates/assets/css/components.css +258 -0
  21. data/lib/docyard/templates/assets/css/layout.css +273 -0
  22. data/lib/docyard/templates/assets/css/main.css +10 -4
  23. data/lib/docyard/templates/assets/css/markdown.css +199 -0
  24. data/lib/docyard/templates/assets/css/reset.css +59 -0
  25. data/lib/docyard/templates/assets/css/typography.css +97 -0
  26. data/lib/docyard/templates/assets/css/variables.css +114 -0
  27. data/lib/docyard/templates/assets/js/theme.js +193 -1
  28. data/lib/docyard/templates/layouts/default.html.erb +41 -19
  29. data/lib/docyard/templates/markdown/core-concepts/file-structure.md.erb +61 -0
  30. data/lib/docyard/templates/markdown/core-concepts/markdown.md.erb +90 -0
  31. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +43 -0
  32. data/lib/docyard/templates/markdown/getting-started/introduction.md.erb +30 -0
  33. data/lib/docyard/templates/markdown/getting-started/quick-start.md.erb +56 -0
  34. data/lib/docyard/templates/markdown/index.md.erb +78 -14
  35. data/lib/docyard/templates/partials/_icons.html.erb +11 -0
  36. data/lib/docyard/templates/partials/_nav_group.html.erb +7 -0
  37. data/lib/docyard/templates/partials/_nav_item.html.erb +3 -0
  38. data/lib/docyard/templates/partials/_nav_leaf.html.erb +1 -0
  39. data/lib/docyard/templates/partials/_nav_list.html.erb +3 -0
  40. data/lib/docyard/templates/partials/_nav_section.html.erb +6 -0
  41. data/lib/docyard/templates/partials/_sidebar.html.erb +6 -0
  42. data/lib/docyard/templates/partials/_sidebar_footer.html.erb +11 -0
  43. data/lib/docyard/utils/path_resolver.rb +30 -0
  44. data/lib/docyard/utils/text_formatter.rb +22 -0
  45. data/lib/docyard/version.rb +1 -1
  46. data/lib/docyard.rb +16 -4
  47. metadata +32 -3
  48. data/lib/docyard/templates/assets/css/syntax.css +0 -116
  49. data/lib/docyard/templates/markdown/getting-started.md.erb +0 -40
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Sidebar
5
+ class TreeBuilder
6
+ attr_reader :docs_path, :current_path, :title_extractor
7
+
8
+ def initialize(docs_path:, current_path:, title_extractor: TitleExtractor.new)
9
+ @docs_path = docs_path
10
+ @current_path = Utils::PathResolver.normalize(current_path)
11
+ @title_extractor = title_extractor
12
+ end
13
+
14
+ def build(file_items)
15
+ transform_items(file_items, "")
16
+ end
17
+
18
+ private
19
+
20
+ def transform_items(items, relative_base)
21
+ items.map do |item|
22
+ if item[:type] == :directory
23
+ transform_directory(item, relative_base)
24
+ else
25
+ transform_file(item, relative_base)
26
+ end
27
+ end
28
+ end
29
+
30
+ def transform_directory(item, relative_base)
31
+ dir_path = File.join(relative_base, item[:name])
32
+
33
+ {
34
+ title: Utils::TextFormatter.titleize(item[:name]),
35
+ path: nil,
36
+ active: false,
37
+ type: :directory,
38
+ collapsible: true,
39
+ collapsed: false,
40
+ children: transform_items(item[:children], dir_path)
41
+ }
42
+ end
43
+
44
+ def transform_file(item, relative_base)
45
+ file_path = File.join(relative_base, "#{item[:name]}#{Constants::MARKDOWN_EXTENSION}")
46
+ full_file_path = File.join(docs_path, file_path)
47
+ url_path = Utils::PathResolver.to_url(file_path.delete_suffix(Constants::MARKDOWN_EXTENSION))
48
+
49
+ {
50
+ title: title_extractor.extract(full_file_path),
51
+ path: url_path,
52
+ active: current_path == url_path,
53
+ type: :file,
54
+ children: []
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "sidebar/file_system_scanner"
4
+ require_relative "sidebar/title_extractor"
5
+ require_relative "sidebar/tree_builder"
6
+ require_relative "sidebar/renderer"
7
+
8
+ module Docyard
9
+ class SidebarBuilder
10
+ attr_reader :docs_path, :current_path, :config
11
+
12
+ def initialize(docs_path:, current_path: "/", config: {})
13
+ @docs_path = docs_path
14
+ @current_path = current_path
15
+ @config = config
16
+ end
17
+
18
+ def tree
19
+ @tree ||= build_tree
20
+ end
21
+
22
+ def to_html
23
+ renderer.render(tree)
24
+ end
25
+
26
+ private
27
+
28
+ def build_tree
29
+ file_items = scanner.scan
30
+ tree_builder.build(file_items)
31
+ end
32
+
33
+ def scanner
34
+ @scanner ||= Sidebar::FileSystemScanner.new(docs_path)
35
+ end
36
+
37
+ def tree_builder
38
+ @tree_builder ||= Sidebar::TreeBuilder.new(
39
+ docs_path: docs_path,
40
+ current_path: current_path
41
+ )
42
+ end
43
+
44
+ def renderer
45
+ @renderer ||= Sidebar::Renderer.new(
46
+ site_title: config[:site_title] || "Documentation"
47
+ )
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,214 @@
1
+ /* Code Block Styles */
2
+
3
+ /* Code blocks */
4
+ .content pre {
5
+ margin: var(--space-6) 0;
6
+ border-radius: var(--radius-lg);
7
+ overflow-x: auto;
8
+ border: 1px solid var(--color-border);
9
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.03);
10
+ }
11
+
12
+ .content pre code {
13
+ display: block;
14
+ padding: var(--space-4);
15
+ font-family: var(--font-mono);
16
+ font-size: var(--font-size-sm);
17
+ line-height: var(--line-height-relaxed);
18
+ background-color: var(--color-code-bg);
19
+ color: var(--color-code-text);
20
+ overflow-x: auto;
21
+ }
22
+
23
+ /* Syntax highlighting - GitHub style */
24
+ .highlight {
25
+ margin: var(--space-6) 0;
26
+ background-color: var(--color-code-bg);
27
+ border-radius: var(--radius-lg);
28
+ border: 1px solid var(--color-border);
29
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.03);
30
+ }
31
+
32
+ .highlight pre {
33
+ margin: 0;
34
+ border: none;
35
+ }
36
+
37
+ .highlight table td {
38
+ padding: 5px;
39
+ }
40
+
41
+ .highlight table pre {
42
+ margin: 0;
43
+ }
44
+
45
+ .highlight,
46
+ .highlight .w {
47
+ color: #24292f;
48
+ background-color: #f6f8fa;
49
+ }
50
+
51
+ /* Keywords */
52
+ .highlight .k,
53
+ .highlight .kd,
54
+ .highlight .kn,
55
+ .highlight .kp,
56
+ .highlight .kr,
57
+ .highlight .kt,
58
+ .highlight .kv {
59
+ color: #cf222e;
60
+ }
61
+
62
+ .highlight .gr {
63
+ color: #f6f8fa;
64
+ }
65
+
66
+ .highlight .gd {
67
+ color: #82071e;
68
+ background-color: #ffebe9;
69
+ }
70
+
71
+ /* Built-in names */
72
+ .highlight .nb,
73
+ .highlight .nc,
74
+ .highlight .no,
75
+ .highlight .nn {
76
+ color: #953800;
77
+ }
78
+
79
+ /* Strings and regex */
80
+ .highlight .sr,
81
+ .highlight .na,
82
+ .highlight .nt {
83
+ color: #116329;
84
+ }
85
+
86
+ .highlight .gi {
87
+ color: #116329;
88
+ background-color: #dafbe1;
89
+ }
90
+
91
+ .highlight .ges {
92
+ font-weight: bold;
93
+ font-style: italic;
94
+ }
95
+
96
+ /* Constants and literals */
97
+ .highlight .kc,
98
+ .highlight .l,
99
+ .highlight .ld,
100
+ .highlight .m,
101
+ .highlight .mb,
102
+ .highlight .mf,
103
+ .highlight .mh,
104
+ .highlight .mi,
105
+ .highlight .il,
106
+ .highlight .mo,
107
+ .highlight .mx,
108
+ .highlight .sb,
109
+ .highlight .bp,
110
+ .highlight .ne,
111
+ .highlight .nl,
112
+ .highlight .py {
113
+ color: #0550ae;
114
+ }
115
+
116
+ /* Variables */
117
+ .highlight .nv,
118
+ .highlight .vc,
119
+ .highlight .vg,
120
+ .highlight .vi,
121
+ .highlight .vm {
122
+ color: #0550ae;
123
+ }
124
+
125
+ /* Operators */
126
+ .highlight .o,
127
+ .highlight .ow {
128
+ color: #0550ae;
129
+ }
130
+
131
+ /* Headers and subheaders in diffs */
132
+ .highlight .gh,
133
+ .highlight .gu {
134
+ color: #0550ae;
135
+ font-weight: bold;
136
+ }
137
+
138
+ /* Strings */
139
+ .highlight .s,
140
+ .highlight .sa,
141
+ .highlight .sc,
142
+ .highlight .dl,
143
+ .highlight .sd,
144
+ .highlight .s2,
145
+ .highlight .se,
146
+ .highlight .sh,
147
+ .highlight .sx,
148
+ .highlight .s1,
149
+ .highlight .ss {
150
+ color: #0a3069;
151
+ }
152
+
153
+ /* Decorators and functions */
154
+ .highlight .nd,
155
+ .highlight .nf,
156
+ .highlight .fm {
157
+ color: #8250df;
158
+ }
159
+
160
+ /* Errors */
161
+ .highlight .err {
162
+ color: #f6f8fa;
163
+ background-color: #82071e;
164
+ }
165
+
166
+ /* Comments */
167
+ .highlight .c,
168
+ .highlight .ch,
169
+ .highlight .cd,
170
+ .highlight .cm,
171
+ .highlight .cp,
172
+ .highlight .cpf,
173
+ .highlight .c1,
174
+ .highlight .cs,
175
+ .highlight .gl,
176
+ .highlight .gt {
177
+ color: #6e7781;
178
+ }
179
+
180
+ /* Name indicators */
181
+ .highlight .ni,
182
+ .highlight .si {
183
+ color: #24292f;
184
+ }
185
+
186
+ /* Generic emphasis */
187
+ .highlight .ge {
188
+ color: #24292f;
189
+ font-style: italic;
190
+ }
191
+
192
+ .highlight .gs {
193
+ color: #24292f;
194
+ font-weight: bold;
195
+ }
196
+
197
+ /* Code block scrollbar */
198
+ .content pre::-webkit-scrollbar {
199
+ height: 8px;
200
+ }
201
+
202
+ .content pre::-webkit-scrollbar-track {
203
+ background: var(--color-bg-secondary);
204
+ border-radius: var(--radius-md);
205
+ }
206
+
207
+ .content pre::-webkit-scrollbar-thumb {
208
+ background: var(--color-border);
209
+ border-radius: var(--radius-md);
210
+ }
211
+
212
+ .content pre::-webkit-scrollbar-thumb:hover {
213
+ background: var(--color-text-tertiary);
214
+ }
@@ -0,0 +1,258 @@
1
+ /* Navigation Components */
2
+
3
+ /* Sidebar footer */
4
+ .sidebar-footer {
5
+ margin-top: auto;
6
+ padding: var(--space-3) var(--space-5) var(--space-5);
7
+ }
8
+
9
+ .sidebar-footer-link {
10
+ display: flex;
11
+ align-items: center;
12
+ gap: var(--space-2);
13
+ text-decoration: none;
14
+ padding: var(--space-2);
15
+ border-radius: var(--radius-md);
16
+ transition: all var(--transition-fast);
17
+ }
18
+
19
+ .sidebar-footer-link:hover {
20
+ text-decoration: none;
21
+ }
22
+
23
+ .sidebar-footer-link:hover .external-icon {
24
+ opacity: 0.5;
25
+ }
26
+
27
+ .sidebar-footer-text {
28
+ flex: 1;
29
+ }
30
+
31
+ .sidebar-footer-title {
32
+ margin: 0;
33
+ font-size: 0.6875rem;
34
+ color: var(--color-text-tertiary);
35
+ font-weight: var(--font-weight-medium);
36
+ line-height: 1.4;
37
+ }
38
+
39
+ .external-icon {
40
+ width: 0.75rem;
41
+ height: 0.75rem;
42
+ color: var(--color-text-secondary);
43
+ opacity: 0;
44
+ transition: opacity var(--transition-fast);
45
+ flex-shrink: 0;
46
+ }
47
+
48
+ /* Sidebar navigation */
49
+ .sidebar nav {
50
+ flex: 1;
51
+ overflow-y: auto;
52
+ }
53
+
54
+ /* Section headers */
55
+ .nav-section {
56
+ margin-bottom: 0;
57
+ padding-bottom: var(--space-5);
58
+ }
59
+
60
+ .nav-section:last-child {
61
+ padding-bottom: 0;
62
+ }
63
+
64
+ .nav-section:first-child {
65
+ margin-top: 0;
66
+ }
67
+
68
+ .nav-section-title {
69
+ font-size: 0.6875rem;
70
+ font-weight: var(--font-weight-semibold);
71
+ color: var(--color-text-tertiary);
72
+ text-transform: uppercase;
73
+ letter-spacing: 0.1em;
74
+ padding: 0 var(--space-3);
75
+ margin-bottom: var(--space-3);
76
+ margin-top: 0;
77
+ pointer-events: none;
78
+ user-select: none;
79
+ }
80
+
81
+ .nav-section:not(:first-child) {
82
+ margin-top: var(--space-3);
83
+ padding-top: 0;
84
+ }
85
+
86
+ .nav-section:not(:first-child) .nav-section-title {
87
+ margin-top: 0;
88
+ }
89
+
90
+ .sidebar nav ul {
91
+ list-style: none;
92
+ padding: 0;
93
+ margin: 0;
94
+ }
95
+
96
+ .sidebar nav li {
97
+ margin: 0;
98
+ }
99
+
100
+ .sidebar nav a {
101
+ display: block;
102
+ padding: 0.5rem var(--space-3);
103
+ font-size: 0.875rem;
104
+ color: var(--color-text-secondary);
105
+ text-decoration: none;
106
+ border-radius: var(--radius-md);
107
+ transition: all var(--transition-fast);
108
+ line-height: 1.4;
109
+ font-weight: var(--font-weight-medium);
110
+ }
111
+
112
+ .sidebar nav a:hover {
113
+ color: var(--color-text);
114
+ text-decoration: none;
115
+ }
116
+
117
+ .sidebar nav a.active {
118
+ color: var(--color-primary);
119
+ font-weight: var(--font-weight-semibold);
120
+ }
121
+
122
+ /* Nested navigation */
123
+ .sidebar nav ul ul {
124
+ margin-left: var(--space-3);
125
+ margin-top: 0.125rem;
126
+ margin-bottom: 0.125rem;
127
+ padding-left: var(--space-4);
128
+ position: relative;
129
+ }
130
+
131
+ .sidebar nav ul ul::before {
132
+ content: '';
133
+ position: absolute;
134
+ left: 0;
135
+ top: 0;
136
+ bottom: 0;
137
+ width: 1px;
138
+ background-color: rgba(148, 163, 184, 0.24);
139
+ }
140
+
141
+ .sidebar nav ul ul li {
142
+ position: relative;
143
+ }
144
+
145
+ .sidebar nav ul ul li:has(> a.active)::before {
146
+ content: '';
147
+ position: absolute;
148
+ left: calc(-1 * var(--space-4));
149
+ top: 0;
150
+ bottom: 0;
151
+ width: 2px;
152
+ background-color: var(--color-primary);
153
+ z-index: 1;
154
+ }
155
+
156
+ .sidebar nav ul ul a {
157
+ font-size: 0.8125rem;
158
+ padding: 0.375rem var(--space-3);
159
+ color: var(--color-text-secondary);
160
+ line-height: 1.5;
161
+ font-weight: var(--font-weight-normal);
162
+ position: relative;
163
+ }
164
+
165
+ .sidebar nav ul ul a:hover {
166
+ color: var(--color-text);
167
+ background-color: transparent;
168
+ }
169
+
170
+ .sidebar nav ul ul a.active {
171
+ color: var(--color-primary);
172
+ background-color: transparent;
173
+ font-weight: var(--font-weight-semibold);
174
+ }
175
+
176
+ /* Top level nav items spacing */
177
+ .sidebar nav>ul>li {
178
+ margin-bottom: 0.125rem;
179
+ }
180
+
181
+ .sidebar nav ul li {
182
+ margin-bottom: 0;
183
+ }
184
+
185
+ /* Collapsible/Accordion items */
186
+ .nav-group-toggle {
187
+ display: flex;
188
+ align-items: center;
189
+ justify-content: space-between;
190
+ width: 100%;
191
+ padding: 0.5rem var(--space-3);
192
+ font-size: 0.875rem;
193
+ color: var(--color-text-secondary);
194
+ text-decoration: none;
195
+ border-radius: var(--radius-md);
196
+ transition: all var(--transition-fast);
197
+ line-height: 1.4;
198
+ font-weight: var(--font-weight-medium);
199
+ background: none;
200
+ border: none;
201
+ text-align: left;
202
+ cursor: pointer;
203
+ }
204
+
205
+ .nav-group-toggle:hover {
206
+ color: var(--color-text);
207
+ }
208
+
209
+ .nav-group-toggle:focus-visible {
210
+ outline: 0.125rem solid var(--color-primary);
211
+ outline-offset: 0.125rem;
212
+ }
213
+
214
+ .nav-group-icon {
215
+ width: 1rem;
216
+ height: 1rem;
217
+ color: var(--color-text-tertiary);
218
+ transition: transform var(--transition-fast);
219
+ flex-shrink: 0;
220
+ }
221
+
222
+ .nav-group-toggle[aria-expanded="true"] .nav-group-icon {
223
+ transform: rotate(90deg);
224
+ }
225
+
226
+ .nav-group-children {
227
+ overflow: hidden;
228
+ transition: max-height var(--transition-base);
229
+ }
230
+
231
+ .nav-group-children.collapsed {
232
+ max-height: 0;
233
+ }
234
+
235
+ /* Mobile menu toggle button */
236
+ .mobile-menu-toggle {
237
+ display: none;
238
+ background: transparent;
239
+ border: none;
240
+ padding: var(--space-2);
241
+ cursor: pointer;
242
+ width: 36px;
243
+ height: 36px;
244
+ align-items: center;
245
+ justify-content: center;
246
+ border-radius: var(--radius-md);
247
+ transition: background-color var(--transition-fast);
248
+ }
249
+
250
+ .mobile-menu-toggle:hover {
251
+ background: var(--color-bg-secondary);
252
+ }
253
+
254
+ @media (max-width: 768px) {
255
+ .mobile-menu-toggle {
256
+ display: flex;
257
+ }
258
+ }